RegExp 向后断言【V8 Blog 翻译计划】

自ECMA-262规范第三版以来,正则表达式自1999年以来一直是Javascript的一部分。在功能性和表现力方面,JavaScript的正则表达式实现大致反映了其他编程语言的实现。

在JavaScript的RegExp中,一个常被忽视,但有时却非常有用的特性是向前查找断言。例如,要匹配后面跟着百分号的数字序列,我们可以使用/\d+(?=%)/。百分符号本身不是匹配结果的一部分。其否定,/\d+(?!%)/,将匹配后面不跟百分号的数字序列:

/\d+(?=%)/.exec('100% of US presidents have been male'); // ['100'] 
/\d+(?!%)/.exec('that’s all 44 of them');                // ['44']

向前查找的反面,向后断言,在JavaScript中是不存在的,但在其他正则表达式实现中是可用的,例如.NET框架。正则表达式引擎为断言内的匹配向后读取,而不是向前读取。可以通过/(?<=\$)\d+/匹配一个在美元符号后的数字序列,其中美元符号不是匹配结果的一部分。其否定,/(?<!\$)\d+/,匹配一个在除美元符号之外的任何字符后的数字序列。

```​javascript /(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill'); // ['100'] /(?<!\$)\d+/.exec('it’s worth about €90');                  // ['90']

一般来说,实现向后断言有两种方式。例如,Perl要求向后模式的长度固定。这意味着不允许使用\*或+等量词。这样,正则表达式引擎可以向后移动固定长度,并以与向前匹配完全相同的方式匹配向后,从向后移动的位置开始。

.NET框架中的正则表达式引擎采用了一个不同的方法。不需要知道向后模式将会匹配多少字符,它只是向后匹配向后模式,同时读取字符反向正常读取方向。这意味着向后模式可以利用完整的正则表达式语法并匹配任意长度的模式。

显然,第二种选择比第一种强大。这就是为什么V8团队,以及为这个特性作出贡献的TC39,同意JavaScript应该采用更具表现力的版本,尽管它的实现稍微复杂一些。

因为向后断言向后匹配,所以有一些微妙的行为否则会被认为是令人惊讶的。例如,一个带有量词的捕获组捕获的是最后一个匹配。通常,这是最右边的匹配。但在向后断言内部,我们从右到左匹配,因此捕获到的是左边的匹配:

/h(?=(\w)+)/.exec('hodor');  // ['h', 'r'] /(?<=(\w)+)r/.exec('hodor'); // ['r', 'h']

一个捕获组在被捕获之后可以通过后引用进行引用。通常,后引用必须在捕获组的右边。否则,它会匹配空字符串,因为还没有捕获到任何东西。然而,在向后断言内部,匹配的方向是反的:

/(?<=(o)d\1)r/.exec('hodor'); // null /(?<=\1d(o))r/.exec('hodor'); // ['r', 'o'] ```