const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
上面代码中,正则表达式里面有三组圆括号。
使用exec方法,就可以将这三组匹配结果提取出来。
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31
组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号(比如matchObj[1])引用,要是组的顺序变了,引用的时候就必须修改序号。
ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。
((log) => {
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
log(matchObj.groups.year); // 1999
log(matchObj.groups.month);// 12
log(matchObj.groups.day); // 31
})(console.log)
上面代码中,具名组匹配在圆括号内部,模式的头部添加问号 + 尖括号 + 组名(?<year>),然后就可以在exec方法返回结果的groups属性上引用该组名。
同时,数字序号(matchObj[1])依然有效。
具名组匹配等于为每一组匹配加上了 ID,便于描述匹配的目的。
如果组的顺序变了,也不用改变匹配后的处理代码。
如果具名组没有匹配,那么对应的groups对象属性会是undefined。
((log) => {
const RE_OPT_A = /^(?<as>a+)?$/;
const matchObj = RE_OPT_A.exec('');
log(matchObj.groups.as); // undefined
log('as' in matchObj.groups); // true
})(console.log)
上面代码中,具名组as没有找到匹配,那么matchObj.groups.as属性值就是undefined,并且as这个键名在groups是始终存在的。
如果要在正则表达式内部引用某个具名组匹配,可以使用\k<组名>的写法。
((log) => {
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
log(RE_TWICE.test('abc!abc')); // true
log(RE_TWICE.test('abc!ab')); // false
})(console.log)
数字引用(\1)依然有效。
((log) => {
const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
log(RE_TWICE.test('abc!abc')); // true
log(RE_TWICE.test('abc!ab')); // false
})(console.log)
这两种引用语法还可以同时使用。
((log) => {
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
log(RE_TWICE.test('abc!abc!abc')); // true
log(RE_TWICE.test('abc!abc!ab')); // false
})(console.log)
有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。
((log) => {
const {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
log(one); // foo
log(two); // bar
})(console.log)
字符串替换时,使用$<组名>引用具名组。
((log) => {
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
log('2015-01-02'.replace(re, '$<day>/$<month>/$<year>')); // '02/01/2015'
})(console.log)
上面代码中,replace方法的第二个参数是一个字符串,而不是正则表达式。
replace方法的第二个参数也可以是函数,该函数的参数序列如下。
((log) => {
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const res = '2015-01-02'.replace(re, (
matched, // 整个匹配结果 2015-01-02
capture1, // 第一个组匹配 2015
capture2, // 第二个组匹配 01
capture3, // 第三个组匹配 02
position, // 匹配开始的位置 0
S, // 原字符串 2015-01-02
groups // 具名组构成的一个对象 {year, month, day}
) => {
const {day, month, year} = groups;
return `${day}/${month}/${year}`;
});
log(res);// 02/01/2015
})(console.log)
具名组匹配在原来的基础上,新增了最后一个函数参数:具名组构成的一个对象。
函数内部可以直接对这个对象进行解构赋值。