PS: 阅读此篇文章前需要具备以下知识:
- 正则表达式的基本语法
- String.prototype.replace()
- String.prototype.match()
捕获组(capturing group)是正则表达式里比较常用,也是比较重要的概念,我个人觉得掌握这部分的知识是非常重要的。
这篇文章内容不会很深入,但是尽量做到简单易懂又全面。接下来的内容主要是围绕以下 7 个点:
1: () 捕获组
2: (?:) non capturing group
3: (?=) positive lookahead
4: (?!) negative lookahead
5: (?<=) positive lookbehind
6: (?<!) negative lookbehind
7: (?=), (?!), (?<=), (?<!) 的捕获
1: () 捕获组
/go+/
以上的正则表达式表示一个字母 g 后面跟上一个或者多个字母 o, 他能匹配 go
或者goooo
。但是如果我们想 + 不只是运用到字母 o 上,而是运用到 go 这个整体上怎么办呢?办法就是给 go 加括号:
/(go)+/
为了全局匹配以及不考虑大小写,我们接下来会给我们的正则加上 ig, 这两个 flag:
let reg = /(go)+/ig;
'go is g gogo'.match(reg); //["go", "gogo"]
在上面的例子里面 (go) 就形成了一个捕获组(capturing group)。接下来看一个使用捕获组的例子来加深对它的理解:
let reg = /(\d{2}).(\d{2}).(\d{4})/;
let originString = '10.25.2017';
reg.test(originString); //true
RegExp.$1; //10
RegExp.$2; //25
RegExp.$2; //2017
在上面这个例子里,我们有三组括号,形成了三个捕获组,正则表达式(在 javaScript 里就是我们的 RegExp)会缓存捕获组所匹配的串,以 $n
表示,n 就代表这第几个捕获组。
假如现在我们有一个需求:把显示格式为 10.25.2017 的时间改为 2017-10-25 格式。
我们知道 String 的 replace()方法经常和正则表达式一起使用。在 replace()方法里,我们可以直接使用捕获组的结果:
let reg = /(\d{2}).(\d{2}).(\d{4})/;
let originString = '10.25.2017';
let newString = originString.replace(reg, '$3-$1-$2');
console.log(newString);//"2017-10-25"
2: (?:) non capturing group 非捕获型分组
有的时候我们可能只想匹配分组,但是并不想缓存(不想捕获)匹配到的结果,就可以在我们的分组模式前面加上?:
。例如上面的时间的例子,我们不想捕获第一个分组的结果,就可以这么做:
let reg = /(?:\d{2}).(\d{2}).(\d{4})/;
let originString = '10.25.2017';
reg.test(originString); //true
RegExp.$1; //25
RegExp.$2; //2017
originString.match(reg);// ["10.25.2017", "25", "2017", index: 0, input: "10.25.2017", groups: undefined]
从上面的例子可以看出,我们的正则表达式依然是匹配的(test()的结果依然为 true),但是 RegExp.$1 不是数字 10,而是 25,因为我们在第一个括号里加了 ?:
,10 就不会被捕获。match() 的执行结果也会受 ?:
的影响:match()的结果里不再有‘10’。
3: (?=) positive lookahead 正向前瞻型捕获
有一个句子:1 apple costs 10€. 我们想要匹配€前面的价格(这里是一个数字),但是注意不能匹配到句子开头的数字 1。这种情况,就可以用到正向前瞻型捕获:
let reg = /\d+(?=€)/g;
let reg1 = /\d+/g;
let str = '1 apple costs 10€';
str.match(reg); //["10"]
str.match(reg1); //["1", "10"]
上面的例子里面 reg1 就只需要匹配数字,对于数字后面跟什么并没有要求,所以它能匹配到 1,10。但是 reg 使用了前瞻型匹配,就只能匹配到 10。
或许你已经能从上面的对比里了解到什么是正向前瞻型捕获了,意思是:
/x(?=y)/ 匹配 x, 但是必须在 x 的【后面】【是】y 的情况下
4: (?!) negative lookahead 负向前瞻型捕获
上面我们了解了什么是正向前瞻型匹配,从字面意思也能猜出来负向前瞻型捕获就是:
/x(?!y)/ 匹配 x, 但是必须在 x 的【后面】【不是】y 的情况下
例如下面的例子,我们要匹配数字 1,而不要€前面的 2,就可以用到?!
:
let reg = /\d+(?!€)/g;
let str = '1 apple costs 2€';
str.match(reg); ['1']
5: (?<=) positive lookbehind 正向后顾型捕获
后顾型和前瞻型正好相反,意思就是:
/(?<=y)x/ 匹配 x, 但是只在【前面】【有】y 的情况下
来看一个例子:
let str = "1 turkey costs $2";
console.log(str.match(/(?<=\$)\d+/g) ); //["2"]
这里的要求是前面有 $ 的数字,所以这里匹配到了数字 2,而没有 1.
6: (?<!) negative lookbehind 负向后顾型捕获
负向就是与正向相反,那么负向后顾型捕获就是:
/(?<=y)x/ 匹配 x, 但是只在【前面】【没有】y 的情况下
来看一个例子:
let str = "1 turkey costs $2";
console.log(str.match(/(?<!\$)\d+/g) ); //['1']
7: (?=), (?!), (?<=), (?<!)的捕获
默认情况下上面的前瞻后顾 4 种都是默认不匹配捕获组里面的内容的,也就是不匹配括号里的条件的。例如我们的正向前瞻 /d+(?=€)/g,只会匹配到数字,并不会匹配到€。如果我们想要也匹配到€怎么办呢?答案就是给€也包上一个括号:
let str = "1 turkey costs 2€";
let reg = /\d+(?=(€))/;
str.match(reg); //["2", "€", index: 15, input: "1 turkey costs 2€", groups: undefined]
这样就匹配到了数字 2 和它后面的€。
下面再来看看后顾型:
let str = "1 turkey costs $2";
let reg = /(?<=(\$|£))\d+/;
console.log(str.match(reg) ); //["2", "$", index: 16, input: "1 turkey costs $2", groups: undefined]
需要特别注意到的一点是,对于后顾型,虽然条件在匹配项的前面,但是匹配出来的结果顺序依然是条件在匹配项的后面。所以这里 match()出来的结果是 2 在 $ 的前面。
PS:截止到目前为止(ES2015),JavaScript 还不支持后顾型匹配,就是说 (?<=), (?<!) 这两种是不支持的。如果你在 webStorm 里面使用可能会得到 error:look-behind groups are not supported in this regex dialect。好消息是 ES2018 已经对其进行了支持,可以参考:http://2ality.com/2017/05/reg…