ES9中正则表达式相关主要有两个提案:正则表达式命名捕获组 & 正则表达式dotAll模式,目前都已进入stage4阶段。
正则表达式命名捕获组
ECMAScript提案“正则表达式命名捕获组”proposal-regexp-named-group 由 Gorkem Yakin, Daniel Ehrenberg负责。
1. 什么是捕获组
捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显示命名的组里,方便后面引用,且这种引用既可以在正则表达式内部,也可以在正则表达式外部。
捕获组有两种形式,一种是普通捕获组,另一种是命名捕获组。
目前JavaScript只支持数字形式的普通捕获组,而这个提案就是为了给JavaScript增加命名捕获组。
2.捕获组编号规则
编号规则指的是以数字为捕获组进行编号的规则, 编号0的捕获组代表正则表达式整体。
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const matchers = regex.exec('2020-12-02');
console.table(matchers)
3.命名捕获组
使用数字捕获组的一个缺点是对于引用不太直观。比如上面的例子,我们相对比较难分清楚哪个组代表年,哪个组代表月或者日。而且,当我们交互了年和月的值时,使用捕获组引用的代码都需要更改。
而命名捕获组就是为了解决这个问题。
命名捕获组可以使用(?<name>...)语法给每个组起个名称。因此,用来匹配日期的正则表达式可以写为:
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
每个捕获组的名字必须唯一,否则会抛出异常:
另外,捕获组的名字必须符合JavaScript命名规范:
命名捕获组可以通过匹配结果的groups
属性访问。
let regx = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let result = regx.exec('2020-12-02');
result.groups.year === '2020';
result.groups.month === '12';
result.groups.day === '02';
result[0] === '2020-12-02';
result[1] === '2020';
result[2] === '12';
result[3] === '02';
使用解构赋值的例子:
let regx = /^(?<one>.*):(?<two>.*)$/;
let {groups: {one, two}} = regx.exec('foo:bar');
console.log(`one: ${one}, two: ${two}`);
4.反向引用
当需要在正则表达式里面引用命名捕获组时,使用\k<name>
语法。
let duplicate = /^(?<half>.*).\k<half>$/;
duplicate.test('a*b'); // false
duplicate.test('a*a'); // true
如果引用一个不存在的命名捕获组,会抛出异常:
命名捕获组也可以和普通数字捕获组一起使用:
let triplicate = /^(?<part>.*).\k<part>.\1$/;
triplicate.test('a*a*a'); // true
triplicate.test('a*a*b'); // false
5.替换
命名捕获组也可以在String.prototype.replace
函数中引用,使用$<name>
语法。
let regx = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let result = "2020-12-02".replace(regx, '$<day>/$<month>/$<year>');
console.log(result === '02/12/2020');
String.prototype.replace
第2个参数可以接受一个函数。这时,命名捕获组的引用会作为 groups
参数传递进取。
第2个参数的函数签名是function (matched, capture1, ..., captureN, position, S, groups)
。
let regx = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let result = '2020-12-02'.replace(regx, (...args)=>{
let { day, month, year } = args[args.length - 1];
return `${day}/${month}/${year}`
});
result === '02/12/2020';
6.命名捕获组未匹配
如果一个可选的命名捕获组没有匹配时,在匹配结果中,此命名组依然存在,值是undefined
。
let regx = /^(?<optional>\d+)?$/;
let matchers = regx.exec('');
matcher[0] === '';
matchers.groups.optional === undefined;
如果捕获组不是可选的,匹配结果是null
。
let regx = /^(?<foo>\d+)$/;
let matchers = regx.exec('');
matchers === null;
7. 向下兼容
/(?<name>)/
和/\k<name>/
只有在命名捕获组中才有意义。如果正则表达式没有命名捕获组,那么/\k<name>/
仅仅是字符串字面量"k<name>"
而已。
8. 实现
- V8, shipping in Chrome 64
- XS, in January 17, 2018 update
- Transpiler (Babel plugin)
- Safari beginning in Safari Technology Preview 40
正则表达式dotAll模式
“正则表达式的 s
(dotAll) flag” 提案 proposal-regexp-dotall-flag 由 Mathias Bynens 负责,目前已经进入 stage 4,并将成为 ES9(ES2018) 的一部分。
在JavaScript正则表达式中.
用来匹配任意单个字符,但是有2个例外:
- 多字节emojo字符
- 行终结符(line terminator characters)
let regx = /^.$/;
regx.test('😊'); // false
我们可以通过设置u
标识来解决,即unicode:
let regx = /^.$/u;
regx.test('😊'); // true
行终结符包含: \n
-换行, \r
-回车,行分隔符,段分隔符。
还有一些其它字符,也可以作为一行的开始:\v
- vertical tab, \f
- form feed 等。
现状
目前JavaScript正则表达式的.
可以匹配其中的一部分字符:
let regx = /./;
regx.test('\n'); // false
regx.test('\r'); // false
regx.test('\v'); // true
regx.test('\f'); // true
在正则表达式中,用于表示字符串开头和字符串结尾的元字符是^
和$
, 因此,一个变通的方式可以使用^
来匹配。
/foo.bar/.test('foo\nbar'); // false
/foo[^]bar/.test('foo\nbar'); // true
还可以使用\s
来匹配空白字符:
/foo.bar/.test('foo\nbar'); // false
/foo[\s]bar/.test('foo\nbar'); // true
新增s标识(dotAll)
在最新的ECMAScript规范中,为JavaScript的正则表达式增加了一个新的标志s
用来表示dotAll, 以使.
可以匹配任意字符。
/foo.bar/s.test('foo\nbar'); // true
let regx = /foo.bar/s; // 等价于 let regx = new RegExp('foo.bar', 's');
regx.test('foo\nbar'); // true
regx.dotAll; // true
regx.flags; // "s"
命名由来
为了实现dotAll功能,其他语言已经使用了s
标识:
- Java使用
Pattern.DOTALL
- C#和VB使用
RegexOptions.Singleline
- Python同时支持
regx.DOTALL
和regx.S
目前在 JavaScript 正则表示中所有修饰符的含义:
- g → global
- i → ignoreCase
- m → multiline
- y → sticky
- u → unicode
- s → dotAll
实现
- V8 - Chrome 62
- JavaScriptCore - Safari Technology Preview 39a
- XS, shipping in Moddable as of the January 17, 2018 update
regexpu (transpiler) with the
{ dotAllFlag: true }
option enabledCompat-transpiler of RegExp Tree
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。