2020 年更新: Javascript 实现开始原生支持 regular expression lookbehinds。 RegExp Lookbehind Assertions 的提案草案已被 ECMAScript 2021 的 ECMA-262 draft specs 接受,已在 Chrome 62+(2017 年 10 月 17 日发布)中实现 in V8 的 Irregexp,并且具有已通过 shim layer 获取 Firefox 78+ 中的 Irregexp(ESR,于 2020 年 6 月 30 日发布)。其他 JS 解释器将跟随。
See more detailed support listings here.
实现后视的传统解决方法
JavaScript 缺乏对 regular expression lookbehinds 的支持,例如 (?<=…)(正)和 (?<!…)(负),但这并不意味着您仍然不能在 JavaScript 中实现这种逻辑。
匹配(非全局)
正向后视匹配:
// from /(?<=foo)bar/i
var matcher = mystring.match( /foo(bar)/i );
if (matcher) {
// do stuff with matcher[1] which is the part that matches "bar"
}
固定宽度负后向匹配:
// from /(?<!foo)bar/i
var matcher = mystring.match( /(?!foo)(?:^.{0,2}|.{3})(bar)/i );
if (matcher) {
// do stuff with matcher[1] ("bar"), which does not follow "foo"
}
可以在没有全局标志的情况下进行负向后查看,但只能使用固定宽度,并且您必须计算该宽度(使用alternations 可能会变得很困难)。使用(?!foo).{3}(bar) 会更简单且大致等效,但它不会匹配以“rebar”开头的行,因为. 无法匹配换行符,因此我们需要上述代码的交替来匹配字符前带有“bar”的行四。
如果您需要可变宽度,请使用以下全局解决方案并在 if 节的末尾放置一个 break。 (此限制很常见。.NET、vim 和 JGsoft 是支持可变宽度后视的 only 正则表达式引擎。PCRE、PHP 和 Perl 仅限于固定宽度。 Python 需要 alternate regex module 来支持这一点。也就是说,下面解决方法的逻辑应该适用于支持正则表达式的所有语言。)
匹配(全局)
当您需要循环给定字符串中的每个匹配项(g 修饰符,全局匹配)时,您必须在每次循环迭代中重新定义 matcher 变量,并且必须使用 RegExp.exec()(使用 RegExp创建了before the loop) 因为String.match() 解释了全局修饰符differently 并会创建一个无限循环!
全球正面回顾:
var re = /foo(bar)/gi; // from /(?<=foo)bar/gi
while ( matcher = re.exec(mystring) ) {
// do stuff with matcher[1] which is the part that matches "bar"
}
“Stuff”当然可以包括填充数组以供进一步使用。
全局否定后视:
var re = /(foo)?bar/gi; // from /(?<!foo)bar/gi
while ( matcher = re.exec(mystring) ) {
if (!matcher[1]) {
// do stuff with matcher[0] ("bar"), which does not follow "foo"
}
}
请注意,cases 并不能完全代表负面的后视。考虑将/(?<!ba)ll/g 与Fall ball bill balll llama 匹配。它将只找到所需四个匹配中的三个,因为当它解析balll 时,它会找到ball,然后在l llama 处继续一个字符。仅当末尾的部分匹配可能会干扰另一端的部分匹配时才会发生这种情况(balll 打破 (ba)?ll 但foobarbar 与(foo)?bar 很好)唯一的解决方案是使用上述固定宽度方法。
更换
Mimicking Lookbehind in JavaScript 是一篇很棒的文章,描述了如何做到这一点。
它甚至有一个后续指向 collection of short functions 在 JS 中实现这一点。
在String.replace() 中实现lookbehind 要容易得多,因为您可以创建anonymous function 作为替换并处理该函数中的lookbehind 逻辑。
这些在第一场比赛中起作用,但只需添加 g 修饰符即可使其成为全局。
正向后视替换:
// assuming you wanted mystring.replace(/(?<=foo)bar/i, "baz"):
mystring = mystring.replace( /(foo)?bar/i,
function ($0, $1) { return ($1 ? $1 + "baz" : $0) }
);
这将获取目标字符串并将bar 的实例替换为baz,只要它们遵循foo。如果匹配,$1 被匹配,并且三元运算符 (?:) 返回匹配的文本和替换文本(但不是 bar 部分)。否则,三元运算符返回原始文本。
负后视替换:
// assuming you wanted mystring.replace(/(?<!foo)bar/i, "baz"):
mystring = mystring.replace( /(foo)?bar/i,
function ($0, $1) { return ($1 ? $0 : "baz") }
);
这本质上是一样的,但由于它是一个负向的后视,它会在$1 丢失时起作用(我们不需要在这里说$1 + "baz",因为我们知道$1 是空的)。
这与其他动态宽度负回溯解决方法具有相同的警告,并且通过使用固定宽度方法进行了类似的修复。