正则表达式的后行断言
JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。ES2018 引入后行断言。
“先行断言”指的是,x
只有在 y
前面才匹配,必须写成/x(?=y)/
。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/
。
“先行否定断言”指的是,x
只有不在 y
前面才匹配,必须写成/x(?!y)/
。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/
。
/\d+(?=%)/.exec('100% of US presidents have been male') // ["100"]
/\d+(?!%)/.exec('that’s all 44 of them') // ["44"]
“后行断言”正好与“先行断言”相反,x
只有在 y
后面才匹配,必须写成/(?<=y)x/
。比如,只匹配美元符号之后的数字,要写成/(?<=\$)\d+/
。
“后行否定断言”则与“先行否定断言”相反,x
只有不在 y
后面才匹配,必须写成/(?<!y)x/
。比如,只匹配不在美元符号后面的数字,要写成/(?<!\$)\d+/
。
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"]
/(?<!\$)\d+/.exec('it’s is worth about €90') // ["90"]
“后行断言”的实现,需要先匹配/(?<=y)x/
的 x
,然后再回到左边,匹配 y
的部分。这种“先右后左”的执行顺序,与所有其他正则操作相反,导致了一些不符合预期的行为。
首先,后行断言的组匹配,与正常情况下结果是不一样的。
/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"]
/^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]
上面代码中,需要捕捉两个组匹配。没有“后行断言”时,第一个括号是贪婪模式,第二个括号只能捕获一个字符,所以结果是 105
和 3
。而“后行断言”时,由于执行顺序是从右到左,第二个括号是贪婪模式,第一个括号只能捕获一个字符,所以结果是 1
和 053
。
其次,“后行断言”的反斜杠引用,也与通常的顺序相反,必须放在对应的那个括号之前。
/(?<=(o)d\1)r/.exec('hodor') // null
/(?<=\1d(o))r/.exec('hodor') // ["r", "o"]
上面代码中,如果后行断言的反斜杠引用(\1
)放在括号的后面,就不会得到匹配结果,必须放在前面才可以。因为后行断言是先从左到右扫描,发现匹配以后再回过头,从右到左完成反斜杠引用。