ECMAScript 提案“正则表达式命名捕获组” proposal-regexp-named-groups 由 Gorkem Yakin, Daniel Ehrenberg 负责,目前已经进入 stage 4,将会是 ES9(ES2018) 的一部分。
1. 什么是捕获组
捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用。而且,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部。
捕获组有两种形式,一种是普通捕获组,另一种是命名捕获组。
目前 JavaScript 只支持数字形式的普通捕获组,而这个提案就是为了给 JavaScript 增加命名捕获组。
2. 捕获组编号规则
编号规则指的是以数字为捕获组进行编号的规则,编号为 0 的捕获组,指的是正则表达式整体。
const regex = /(\d{4})-(\d{2})-(\d{2})/; |
3. 存在的缺点
使用数字捕获组的一个缺点是对于引用不太直观,以上面的例子,我们很难分清楚哪个组代表的是年,哪个组代表的是月。而且当我们交互了年和月的值时,使用捕获组引用的代码都需要更改。
而命名捕获组就是为了解决这个问题。
4. 命名捕获组
命名捕获组可以使用 (?<name>...)
语法给每个组起一个名字。
因此,用来匹配日期的正则表达式可以写为:
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/ |
每个捕获组的名字必须唯一。否则会抛出异常:
/(?<foo>\d)-(?<foo>\d)/; |
捕获组的名字必须符合 JavaScript 的命名规范:
/(?<666>\d)-(?<bar>\d)/; |
命名捕获组可以通过匹配结果的 groups
属性访问。
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; |
如果使用解构赋值,还可以这样写:
let re = /^(?<one>.*):(?<two>.*)$/; |
5. 反向引用
当需要在正则表达式里面引用命名捕获组时,使用 \k<name>
语法。
例如:
let duplicate = /^(?<half>.*).\k<half>$/; |
如果引用一个不存在的命名捕获组时,会抛出异常:
/^(?<foo>.*).\k<bar>$/; |
命名捕获组也可以和普通数字捕获组一起使用:
let triplicate = /^(?<part>.*).\k<part>.\1$/; |
6. 替换
命名捕获组也可以在 String.prototype.replace
函数中引用。使用 $<name>
语法。
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; |
String.prototype.replace
第 2 个参数可以接受一个函数。这时 命名捕获组的引用会作为 groups
参数传递进去。
第 2 个参数的函数签名是 function (matched, capture1, ..., captureN, position, S, groups)
。
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; |
7. 命名捕获组未匹配
如果一个可选的命名捕获组没有匹配时,在匹配结果中,此命名组依然存在,值是 undefined
。
let re = /^(?<optional>\d+)?$/ |
(?<optional>\d+)?
最后面的 ?
表示匹配前面的模式 0 次或 1 次。
如果捕获组不是可选的,匹配结果是 null
。
let re = /^(?<foo>\d+)$/ |
8. 向下兼容
/(?<name>)/
和 /\k<foo>/
只有在命名捕获组中才有意义。如果正则表达式没有命名捕获组,那么 /\k<foo>/
仅仅是字符串字面量"k<foo>"
而已。
/\k<foo>/.test('k<foo>'); // true |
9. 实现
- V8, shipping in Chrome 64
- XS, in January 17, 2018 update
- Transpiler (Babel plugin)
- Safari beginning in Safari Technology Preview 40