正则字符串
概述
正则表达式是一段特殊规则的字符串,它可以在一个字符串中找出满足指定规则的字符串子集,在处理字符串时,正则表达式有非常强的能力。
比如处理一个日期字符串,我们需要分离其年月日,如下:
var date = "2000-01-02";
var arr = date.split("-");
console.log("年", arr[0]);
console.log("月", arr[1]);
console.log("日", arr[2]);
但是现在出现了新的情况,数据格式不严谨,数据可能是以下样子:
var dates = ["2000-01-02", "2000/01/02", "2000.01.02", "2000-1-2"];
此时针对不同类型的日期格式,我们需要使用不同的方式去分割,并且在此之前,我们可能还不知道它们具体是什么格式,此时,一个正则字符串解决上述所有问题:
var dates = ["2000-01-02", "2000/01/02", "2000.01.02", "2000-1-2"];
for (var i = 0; i < dates.length; i++) {
var m = dates[i].match(/^(\d{4}).(\d{1,2}).(\d{1,2})$/);
if (m !== null) {
console.log("年", m[1]);
console.log("月", m[2]);
console.log("日", m[3]);
}
}
在上面代码中,/^(\d{4}).(\d?\d).(\d?\d)$/
这一串火星文就是正则字符串,它表示一种规则,当字符串满足此规则,它会对其进行截取和分割。
现在简单的了解下这段正则字符串:
/^(\d{4}).(\d?\d).(\d?\d)$/;
- 首尾的反斜杠
/
是用来声明正则字符串,就类似普通字符串的单双引号。 - 开头的上尖号
^
指定字符串必须按照后续规则开头,也就是限制字符串的开头。 - 末尾的
$
和上尖号^
类似,表示了必须按照这个规则结尾,也就是限制字符串的结尾。 \d{4}
表示四个连续的数字,这个规则对应了年。- 圆括号用来包裹一组规则,表示这段规则记作一组。正则字符串会在查找后把每组规则内的字符串会依次分割出来,所以后续我们可以根据数组访问
m[1]
、m[2]
和m[3]
,对应了年月日三组数据。 - 小数点
.
表示任何字符,正好对应了我们日期分隔符的不同。 \d{1,2}
表示连续 1 到 2 个数字,对应了日期的月日缺省前缀 0 的情况,比如dates
的最后一个值。
把上面的话合并一下,这串正则表达式的意思就是:以 4 个数字开头,连接任意一个字符,连接一个或者两个数字,连接任意一个字符,连接一个或者两个数字结尾的字符串。
实际上正则表达式真的真的很简单,希望大家学习本课程千万不要有负担,不要被如火星文一般的表示方式迷惑,只要学习完正则的语法,它完全是可读的。
备注:大部分程序语言的正则字符串都是统一参照 PCRE 标准,JS 中也是,所以你学习完正则字符串后,以后也可以在其他语言使用。如果你已经学习过 PCRE 标准的正则,那么可以直接跳过学习部分,去看使用章节。
备注:为了简化,正则字符串后面简化为正则。
创建与 match 函数
正则的创建有两种方式:
- 字面量创建:使用反斜杠直接包裹
- 构造函数创建:使用
RegExp
构造函数
var reg1 = /12345/;
var reg2 = new RegExp("12345");
上面两种方式都可以创建正则,第一种是字面量创建,第二种是函数创建,RegExp
函数接受一个普通字符串当做正则的规则。从第二种方式可以看出,正则在 JS 中是引用类型,它不是字符串类型。
每个字符串都可以使用 match
函数来根据正则查找,match
函数存在于 String.prototype
上。它的使用如下:
var m = "abc 123 ABC".match(/123/);
console.log(m);
match
函数会根据正则查找字符串,然后返回查找结果,如果不成功,返回一个 null
。
备注:在正则中,把查找的过程称作匹配,后续介绍中,都会使用匹配一词代替查找。
规则
普通匹配
普通匹配就如同查找字符串,比如:
var foo = "abc 123 ABC";
var m = foo.match(/123/);
console.log(m);
正则 /123/
的规则是字符串 '123
',上面代码中,会在 foo
字符串中查找字符串 '123
',如果匹配成功,返回匹配的字符串。
大部分字符在正则中都没规则意义,它们用于指定匹配的内容,比如数字,大小写字母,汉字等。
特殊规则字符
在正则中,一部分字符或者转义字符具有特殊的意义,它们表示一定的规则,以下是常用具有规则意义的字符:
规则字符 | 描述 |
---|---|
^ | 匹配字符串的开头,用以决定字符串由什么规则开始,比如 /^abc/ ,可以匹配 abcdefe ,但是不能匹配 1abcd 。 |
$ | 匹配字符串的结尾,用以决定字符串由什么规则结束,比如 /abc$/ ,可以匹配 123abc ,但不能匹配 abcd 。当它和 ^ 配合使用时,就限制了整个字符串的开头的结尾。 |
. | 匹配任何字符,比如 /a.c/ 就可以匹配到 123aac ,中的 aac ,或者匹配到 a_b456 中的 a_b 。 |
* | 匹配前一个规则 0 次或多次,比如 /a*b/ 可以匹配字符串中的 b 、ab 、aaaab |
+ | 匹配前一个规则 1 次或多次,比如 /a+b/ 可以匹配字符串中的 ab 、aaaaaab ,但是不能匹配到 b ,因为 b 中没有出现 a 字符 |
? | 匹配前一个规则 0 次或 1 次,比如 /a?b/ 可以匹配字符串中的 b 和 ab ,但不能匹配到 abb |
\d | 匹配一个数字,比如 /1\d\d/ 可以匹配字符串中的 123 、100 ,例如在 ab19876 中,可以匹配到 198 三个数字。 |
\D | 匹配一个非数字,比如 /\D\d/ 可以匹配字符串中的 h1 、h2 、c0 ,例如在 123@123.com 中,可以匹配到 @1 这两个字符。 |
\s | 匹配一个空白符,比如换行空格【tab】制表符,比如 /abc\s/ 可以匹配 abc abc! 中的 abc ,而不能匹配 abc! |
\S | 匹配一个非空白符,比如 /abc\S/ 可以匹配 abc abc! 中的 abc! 。 |
\w | 匹配大小写字母数字和下划线,比如 /abc\w123/ 可以匹配字符串中的 abc_123 、abcA123 ,但是不能匹配abc@123 、abc!123 |
\W | 匹配大小写字母数字和下划线之外的字符,比如 /abc\w123/ 可以匹配字符串中的 abc@123 、abc!123 。 |
\n | 匹配换行符。 |
\r | 匹配回车符。 |
\t | 匹配水平制表符。 |
\ | 转义字符,可以把特殊字符转义为普通字符,比如需要匹配 JS 的文件,可以使用 /.+\.js$/ ,它会匹配如 app.js 、pro.js 等字符串。同理,\^$*+?{}[] 等字符也可以使用正斜杠转义。 |
\b | 匹配单词边界,比如 /\bpen\b/ 只能能够单词 pen ,例如 a pen,a box 中可以匹配 pen ,pen 的左边是空格,右边是逗号,一般很难处理,但是使用 \b 就可以很容易匹配。 |
{n} | 匹配前一个规则 n 次,比如 /fo{2}/ 可以匹配字符串中的 foo ,例如在 bars foos 中,foo 将会被匹配。 |
{n,} | 匹配前一个规则多次,但是至少 n 次,比如 /fo{2,}/ 可以匹配 foo 、fooo 、fooooo ,其中字符 o 不能匹配 2 次以下。 |
{n,m} | 匹配前一个规则 n 到 m 次,比如 /fo{2,3}/ 就只能匹配 foo 、与 fooo |
x\|y | 匹配规则 x 或者规则 y,比如 /1a\|b1/ 可以匹配 1a1 或者 1b1 。 |
[xyz] | 一个规则集合,匹配其中的任意规则,比如:/1[abc]1/ 可以匹配 1a1 、1b1 、1c1 。在规则集合中,可以使用扩折号来指定字母或者数字范围,比如 /[a-z0-9A-Z_]/ 就等同于 /\w/ |
[^xyz] | 匹配一个规则集合的补集,比如:/1[^abc]1/ 可以匹配 1!1 、1@1 ,但是不能匹配 1a1 。 |
(x) | 匹配一个模式,可以理解成一个规则组。比如 /(ab){2}/ 可以匹配 abab ,如果不使用圆括号,{} 只能修饰前一个字符 b ,而不是 ab 。模式可以配合其他特殊字符使用,比如 /(fo*){3}/ 可以匹配 fff 、foffoo ,foofooofoooo 。 |
\数字 | 匹配一个模式的结果,数字指定了匹配哪一个模式,比如 /(\d)a\1/ 中,\1 表示匹配第一个模式的结果,其中第一个模式就是 (\d) ,所以 /(\d)a\1/ 可以匹配诸如 1a1 ,9a9 这样的字符串,但是不能匹配 1a2 、2a7 ,因为 \1 匹配的是模式的结果,不是模式的规则。 |
(?:x) | 匹配一个模式,但是不记录该模式,比如 /(?:foo)(bar) \1/ 中, \1 找到的第一个模式将会是 (bar) ,而不是 (?:foo) ,(?:foo) 的作用和 (foo) 一样,但是它不被记录。 |
x(?=y) | 匹配规则 x,但是需要规则 xy 成立。比如 /foo(?=bar)/ ,对于字符串 foo foobar ,它会匹配第二个 foo ,因为 foo 后必须要有 bar 字符串。 |
(?<=y)x | 匹配规则 x,但是需要规则 yx 成立。比如 /(?<=foo)bar/ ,对于字符串 bar foobar ,它只会匹配第二个 bar ,因为 bar 前面必须要有 foo 字符串。 |
x(?!y) | 匹配规则 x,但是需要规则 xy 不成立。比如 /foo(?!bar)/ ,对于字符串 foobar foo ,他只会匹配第二个 foo ,因为 foo 后面不能有 bar 字符串。 |
(?<!y)x | 匹配规则 x,但是需要规则 yx 不成立。写烦了不写例子了,前三个规则和这个规则使用并不频繁,需要使用的时候查表即可。 |
以上是大部分正则中的特殊字符,使用较频繁。但是还没有列举完全,部分特殊字符使用非常少,可以 点此查表。
贪婪模式
默认情况下,JS 中的正则匹配是贪婪模式,贪婪模式会尽可能多的匹配字符串。在使用 *
、+
、{n,}
时,它们会尽可能多的匹配,比如:
var re = "foo 123 foo abc foo zzz foo".match(/foo.*foo/);
console.log(re);
匹配的结果是整个字符串,从开头的 foo
到结尾的 foo
中间,都会被 .*
匹配。这种情况的匹配,称作贪婪模式。
可以在 *
、+
、{n,}
后面使用 ?
来解除贪婪模式,让其匹配最短字符串,比如:
var re = "foo 123 foo abc foo zzz foo".match(/foo.*?foo/);
console.log(re);
上面的正则只会匹配 foo 123 foo
,然后就停止匹配。
模式修正符
正则匹配时,还区分大小写,匹配成功的时候也会停止匹配,这些规则是可以调整的,使用讴歌 模式修正符 可以修改正则的匹配模式,模式修正符被放到正则的末尾,反斜杠 /
的后面。
JS 中有常用模式修正符如下:
修正符 | 描述 |
---|---|
g | 全局匹配,不会在匹配成功后停止匹配。 |
i | 忽略大小写,比如 /abc/i 此时可以匹配到 ABC 、Abc 、aBC 等。 |
m | 多行匹配,配合 ^ 和 $ 使用,可以匹配任何一行的开头和结尾。比如 /^bar/ 不能匹配 foo\nbar 中的 bar 字符串,但是 /^bar/m 就可以匹配,因为 \n 换行了,bar 字符串是第二行的开头。 |
s | 让 . 可以匹配换行符,默认情况下 /.*/ 只能匹配 foo\nbar 的 foo ,不匹配换行符,但是 /.*/s 就可匹配整个字符串 foo\nbar 。 |
如果使用 RegExp
创建正则,模式修正符放在第二个参数:
var reg = new RegExp(".*", "s");
var re = "foo\nbar".match(reg);
模式修正符还有两个,它们可能并不常有或者是新标准的规定,我不会对其介绍,如果你非常非常有兴趣,可以看看:
修正符 | 描述 |
---|---|
u | 是否开启 Unicode 模式匹配。Unicode 是一种字符集,开启此模式可以按一定模式匹配 Unicode 字符。比如中文 '是 ' 的 Unicode 编码是 662f ,当我们匹配中文 '是 ' 这个字的时候,可以使用 '是'.match(/\u{662f}/u) 来匹配,可以匹配到中文 '是 ' 这个字。如果不开启 Unicode 模式,代码 '是'.match(/\u{662f}/) 会得到一个 null ,\u{xxxx} 这种写法不生效。一般情况下,很少使用这种写法,因为 : - '是'.match(/\u662f/) - '是'.match(/是/) - '\u662f'.match(/是/) - '\u662f'.match(/\u662f/) 四种写法都可以匹配中文的 ' 是 ' 字 |
y | 点击这里 |
使用
RegExp
在 JS 中,正则是引用类型,它们由 RegExp
构造而来,创建一个正则有以下方式:
var reg1 = /foo/i;
// or
var reg2 = new Regexp("foo", "i");
// or
var reg2 = new Regexp(/foo/, "i");
RegExp
有两个常用的函数:
函数 | 描述 |
---|---|
exec | 匹配一段字符串,匹配成功返回正则匹配结果,失败返回 null 。类似字符串的 match 函数 |
test | 匹配一段字符串,是返回 true ,反之 false 。 |
var reg = /\w+@\w+\./;
var htmlStr = "<p> <span>hello</span> </p>";
console.log(reg.exec(htmlStr));
console.log(reg.test(htmlStr));
其中 exec
函数在匹配成功时,返回的对象是一个数组,它的下标和属性对应以下结果:
下标或属性 | 描述 |
---|---|
0 | 匹配的字符串 |
1~n | 各个模式中匹配的字符串 |
index | 匹配字符串的下标 |
input | 原始字符串 |
字符串函数
字符串的一部分函数比如查找,替换等,也支持正则字符串:
函数 | 描述 |
---|---|
match | 匹配一个正则,成功返回匹配结果,失败返回 null 。当设置全局匹配时,match 只会得到一个包含所有匹配到的字符串的数组。 |
matchAll | 匹配一个含有全局匹配的正则,返回一个迭代对象。成功或者失败都会返回一个迭代对象。(迭代对象属于新标准中的知识,再次不多介绍) |
search | 按照正则查找字符串,函数功能不变。 |
replace | 按照规则替换字符串,函数功能不变。 |
split | 按照规则分割字符串,函数功能不变。 |
演示:
var str = "foobarfoobarfoobar";
var reg = /(oo)../g;
console.log(str.match(reg));
// 迭代对象可以使用 for of 遍历,for of 也是新标准中的语法,本系列课程不介绍
var mAll = str.matchAll(reg);
for (var item of mAll) {
console.log(item);
}
console.log(str.search(reg));
console.log(str.replace(reg, " replace "));
console.log(str.split(reg));
性能与应用
性能
使用正则字符串实际上非常耗费性能,在一对多的服务中,很少使用正则,比如服务器,大量正则的使用是给计算机带来很大负担。但是 JS 是客户端语言,负担会小很多。
应用
正则一般都是用来处理有一定规则的文本,比如日期,邮箱,网址,手机号等等,使用正则分解这些文本会变得异常简单,比如本页最开始的段落,我们处理日期一样。
尝试编写一个匹配邮箱的正则:邮箱的规则一般分为两部分,@
符前面表示账户,@
符后表示邮箱的主机(域名),比如 QQ 邮箱就是 xxxxx@qq.com,其中账户或者主机一般都由数字、字母、下划线和扩折号 -
组成,主机至少由两个单词组成,中间用点连接。所以一个邮箱的规则可以写成:
var emailReg = /^[\w-]+@[\w-]+(\.[\w-]+)+$/;
console.log(
emailReg.test("123456@163.com"), //true
emailReg.test("123456@gmail.com"), // true
emailReg.test("123456@qq.com"), // true
emailReg.test("123456@mail.com.cn"), // true
emailReg.test("@main.com"), // false
emailReg.test("123456@com"), // false
emailReg.test("123456mail.com") // false
);
如果你的项目有验证邮箱的需要,那么可以根据上述方式来判断表单是否提交,比如:
<form id="form">
<p>申请账号需要一个邮箱</p>
<input id="mail" type="text" name="mail" />
<button>确定</button>
</form>
<script>
var emailReg = /^[\w-]+@[\w-]+(\.[\w-]+)+$/;
var formEl = document.getElementById("form");
var inputEl = document.getElementById("mail");
formEl.addEventListener("submit", function (e) {
if (emailReg.test(inputEl.value)) {
// 提交表单
alert("验证成功,即将提交。");
} else {
alert("邮箱格式不正确,请重新输入。");
e.preventDefault();
}
});
</script>