模糊匹配
横向模糊匹配
特点:一个正则可匹配的字符串的长度不固定。
其实现的方式是使用量词。譬如 {m,n}
,表示连续出现最少 m 次,最多 n 次。
比如正则/ab{2,5}c/
表示匹配这样一个字符串:第一个字符是 “a”,接下来是 2 到 5 个 “b”,最后是字符 “c”。
纵向模糊匹配
特点:一个正则匹配的字符串,具体到某一位字符时,可以有多种可能。
其实现的方式是使用字符组。譬如[abc]
,表示该字符是可以字符 "a"、"b"、"c"
中的任何一个。
比如/a[123]b/
可以匹配如下三种字符串:"a1b"、"a2b"、"a3b"
。
字符组
字符组(字符类),只表示组内的一个字符。
例如[abc]
,表示匹配一个字符,它可以是 "a"、"b"、"c"
之一。
范围表示
如果字符组里的字符特别多,可以使用范围表示法。
比如[123456abcdefGHIJKLM]
,可以写成 [1-6a-fG-M]
。用连字符-
来省略和简写。
因为连字符有特殊用途,那么要匹配 "a"、"-"、"z"
,
可以写成如下的方式:[-az]
或 [az-]
或 [a\-z]
。
即要么放在开头,要么放在结尾,要么转义,不让引擎认为是范围表示法就行了。
排除字符
纵向模糊匹配,还有一种情形就是,某位字符可以是任何东西,但就不能是"a"、"b"、"c"
。
此时就是排除字符组(反义字符组)的概念。例如[^abc]
,表示是一个除 "a"、"b"、"c"
之外的任意一个字符。字符组的第一位放^
(脱字符),表示求反的概念。
字符组的简写
字符组 | 具体含义 |
---|---|
\d | 表示 [0-9] ,是一位数字 |
\D | 表示 [^0-9] ,除数字外的任意字符 |
\w | 表示 [0-9a-zA-Z_] 。表示数字、大小写字母和下划线。 |
\W | 表示 [^0-9a-zA-Z_] 。非单词字符。 |
\s | 表示 [ \t\v\n\r\f] 。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。 |
\S | 表示 [^ \t\v\n\r\f] 。 非空白符 |
. | 表示 [^\n\r\u2028\u2029] 。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外 |
可以使用[\d\D]
、[\w\W]
、[\s\S]
和 [^]
中任何的一个来代表匹配任意一个字符。
量词
简写形式
量词 | 具体含义 |
---|---|
{m,} | 表示至少出现 m 次 |
{m} | 等价于{m,m} ,表示出现 m 次 |
? | 等价于{0,1} ,表示出现或者不出现 |
+ | 等价于{1,} ,表示出现至少一次 |
* | 等价于{0,} ,表示出现任意次,有可能不出现 |
贪婪匹配与惰性匹配
var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["123", "1234", "12345", "12345"]
上面例子的正则表达式/\d{2,5}/g
可以匹配2-5个数字字符,但它是贪婪的,会尽可能多的匹配更多的字符。
var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["12", "12", "34", "12", "34", "12", "34", "56"]
其中/\d{2,5}?/
表示,虽然 2 到 5 次都行,当2个就够的时候,就不再往下尝试了。
在量词后面增加?
,即可将两次变为贪婪量词
多选分支
具体形式如下:(p1|p2|p3)
,其中 p1
、p2
和 p3
是子模式,用 |
(管道符)分隔,表示其中任何之一。
例如要匹配字符串"good"
和 "nice"
可以使用 /good|nice/
注意,比如我用/good|goodbye/
,去匹配"goodbye"
字符串时,结果是 "good"
,而把正则改成/goodbye|good/
,结果是:"goodbye"
。
也就是说,分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。
位置匹配
位置(锚)是相邻字符之间的位置。比如,下图中箭头所指的地方:
在 ES5 中,共有 6 个锚:^、$、\b、\B、(?=p)、(?=p)
锚 | 具体含义 |
---|---|
^ | 匹配开头,在多行匹配中匹配行开头 |
$ | 匹配结尾,在多行匹配中匹配行结尾 |
\b | 是单词边界,具体就是\w 与\W 之间的位置,也包括 \w 与 ^ 之间的位置,和\w 与 $之间的位置 |
\B | \B 就是\b 的反面的意思,非单词边界。在字符串中所有位置中,扣掉 \b ,剩下的都是\B 的 |
(?=p) | (?=p) ,其中p 是一个子模式,即p 前面的位置,或者说,该位置后面的字符要匹配 p |
(?=p) | 而(?!p) 就是 (?=p) 的反面意思 |
分组和分支结构
分组
/a+/
匹配连续出现的 "a"
,而要匹配连续出现的 "ab"
时,需要使用 /(ab)+/
。
其中括号是提供分组功能,使量词 +
作用于 "ab"
这个整体
分支结构
而在多选分支结构 (p1|p2)
中,此处括号的作用也是不言而喻的,提供了分支表达式的所有可能。
分组引用
这是括号一个重要的作用,有了它,我们就可以进行数据提取,以及更强大的替换操作。
而要使用它带来的好处,必须配合使用实现环境的 API。
以日期为例。假设格式是 yyyy-mm-dd
的,我们可以先写一个简单的正则
var regex = /\d{4}-\d{2}-\d{2}/;
然后再修改成括号版的:
var regex = /(\d{4})-(\d{2})-(\d{2})/;
在匹配过程中,正则引擎给每一个分组都开辟一个空间,用来存储每一个分组匹配到的数据。
既然分组可以捕获数据,那么我们就可以使用它们。
提取数据
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( string.match(regex) );
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]
match
返回的一个数组,第一个元素是整体匹配结果,然后是各个分组(括号里)匹配的内容,然后是匹配下标,最后是输入的文本。另外,正则表达式是否有修饰符 g
,match
返回的数组格式是不一样的。
同时,也可以使用构造函数的全局属性 $1 至 $9 来获取:
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
regex.test(string); // 正则操作即可,例如
//regex.exec(string);
//string.match(regex);
console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "06"
console.log(RegExp.$3); // "12"
替换
比如,想把yyyy-mm-dd
格式,替换成mm/dd/yyyy
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result);
// => "06/12/2017"
其中replace
中的,第二个参数里用$1、$2、$3指代相应的分组。等价于如下的两种形式:
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, function () {
return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
});
console.log(result);
// => "06/12/2017"
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, function (match, year, month, day) {
return month + "/" + day + "/" + year;
});
console.log(result);
// => "06/12/2017"
反向引用
除了使用相应 API 来引用分组,也可以在正则本身里引用分组。但只能引用之前出现的分组,即反向引用。
还是以日期为例。
比如要写一个正则支持匹配如下三种格式:
2016-06-12
2016/06/12
2016.06.12
最先可能想到的正则是:
var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // true
虽然匹配了要求的情况,但也匹配 "2016-06/12"
这样的数据。
假设我们想要求分割符前后一致怎么办?此时需要使用反向引用:
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false
注意里面的 \1
,表示的引用之前的那个分组 (-|\/|\.)
。不管它匹配到什么(比如-
),\1
都匹配那个同
样的具体某个字符。
我们知道了 \1
的含义后,那么\2
和\3
的概念也就理解了,即分别指代第二个和第三个分组。
括号嵌套情况
var regex = /^((\d)(\d(\d)))\1\2\3\4$/;
var string = "1231231233";
console.log( regex.test(string) ); // true
console.log( RegExp.$1 ); // 123
console.log( RegExp.$2 ); // 1
console.log( RegExp.$3 ); // 23
console.log( RegExp.$4 ); // 3
我们可以看看这个正则匹配模式:
第一个字符是数字,比如说"1"
,
第二个字符是数字,比如说"2"
,
第三个字符是数字,比如说"3"
,
接下来的是 \1
,是第一个分组内容,那么看第一个开括号对应的分组是什么,是"123"
,
接下来的是 \2
,找到第2个开括号,对应的分组,匹配的内容是 "1"
,
接下来的是 \3
,找到第3个开括号,对应的分组,匹配的内容是"23"
,
最后的是\4
,找到第3个开括号,对应的分组,匹配的内容是"3"
。