正则表达式lookbehind在JavaScript中无效[重复]

问题描述 投票:0回答:2

考虑使用正向回顾:

var re = /(?<=foo)bar/gi;

这是 Plunker 中无效的正则表达式。为什么?

javascript regex regex-lookarounds
2个回答
21
投票

2020 更新:Javascript 实现开始原生支持正则表达式lookbehindsRegExp Lookbehind Assertions 的提案草案已被 ECMAScript 2021 的 ECMA-262 草案规范接受,已在 Chrome 62+ 中的 V8Irregexp 中实现(2017 年 10 月 17 日发布),这是通过 Firefox 78+ 中的 Irregexp 的 shim 层拾取的(ESR,于 2020 年 6 月 30 日发布)。其他 JS 解释器将会跟进。

在此处查看更详细的支持列表


实现lookbehinds的传统解决方法

JavaScript 缺乏对

正则表达式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" }
负向后查找可以在没有全局标志的情况下完成,但只能使用固定宽度,并且

you必须计算该宽度(这可能会因为alternations而变得困难)。使用 (?!foo).{3}(bar)

 会更简单且大致等效,但它不会匹配以“rebar”开头的行,因为 
.
 无法匹配换行符,因此我们需要上面的代码的交替来匹配字符前具有“bar”的行四个。

如果您需要可变宽度,请使用下面的全局解决方案,并在

break

 节的末尾放置 
if
。 (这个限制很常见。
.NETvimJGsoft唯一支持可变宽度后向查找的正则表达式引擎。PCREPHPPerl仅限于固定宽度。Python需要一个备用正则表达式模块来支持这一点。也就是说,下面的解决方法的逻辑应该适用于所有支持正则表达式的语言。)

匹配(全局)

当您需要循环给定字符串中的每个匹配时(

g

修饰符,全局匹配),您必须在每次循环迭代中重新定义
matcher
变量,并且必须使用
RegExp.exec()
(与RegExp创建
在循环之前),因为String.match()
以不同方式解释全局修饰符
并且将创建无限循环! 全球正向回顾:

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" }

“东西”当然可能包括填充数组以供进一步使用。

全球负面回顾:

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" } }

请注意,在某些
情况下

,这并不能完全代表负面的后视。考虑 /(?<!ba)ll/g

Fall ball bill balll llama
匹配。它只会找到所需的四个匹配项中的三个,因为当它解析
balll
时,它会找到
ball
,然后在
l llama
后面继续一个字符。仅当末尾的部分匹配可能会干扰另一端的部分匹配时才会发生这种情况(
balll
打破
(ba)?ll
foobarbar
(foo)?bar
很好)唯一的解决方案是使用上面的固定方法宽度法。
更换

在 JavaScript 中模仿 Lookbehind

是一篇很棒的文章,描述了如何做到这一点。 它甚至还有一个指向在 JS 中实现此功能的短函数集合
的后续函数。

String.replace()

中实现lookbehind要容易得多,因为您可以创建一个

匿名函数
作为替换并处理该函数中的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
是空的)。
这与其他动态宽度负回顾解决方法具有相同的警告,并且通过使用固定宽度方法进行类似的修复。


1
投票

var s = '<span class="css">55</span> 2 >= 1 2 > 1'; var doc = document.createDocumentFragment(); var wrapper = document.createElement('myelt'); wrapper.innerHTML = s; doc.appendChild( wrapper ); function textNodesUnder(el){ var n, walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false); while(n=walk.nextNode()) { if (n.parentNode.nodeName.toLowerCase() === 'myelt') n.nodeValue = n.nodeValue.replace(/>=?/g, "EQUAL"); } return el.firstChild.innerHTML; } var res = textNodesUnder(doc); console.log(res); alert(res);

© www.soinside.com 2019 - 2024. All rights reserved.