来自Javascript: The Definitive Guide:
但是,当
regexp
是全局正则表达式时,exec()
的行为方式稍微复杂一些。它开始在string
lastIndex
属性指定的字符位置搜索regexp
。找到匹配项后,会将lastIndex
设置为匹配后第一个字符的位置。
我认为定期使用javascript RegExps的任何人都会认出这段话。但是,我在这种方法中发现了一种奇怪的行为。
请考虑以下代码:
>> rx = /^(.*)$/mg
>> tx = 'foo\n\nbar'
>> rx.exec(tx)
[foo,foo]
>> rx.lastIndex
3
>> rx.exec(tx)
[,]
>> rx.lastIndex
4
>> rx.exec(tx)
[,]
>> rx.lastIndex
4
>> rx.exec(tx)
[,]
>> rx.lastIndex
4
RegExp似乎卡在第二行,并没有增加lastIndex
属性。这似乎与The Rhino Book相矛盾。如果我自己设置如下,它会继续并最终按预期返回null,但似乎我不应该这样做。
>> rx.lastIndex = 5
5
>> rx.exec(tx)
[bar,bar]
>> rx.lastIndex
8
>> rx.exec(tx)
null
显然,只要匹配为空字符串,我就可以增加lastIndex
属性。但是,作为好奇的类型,我想知道为什么它不会被exec
方法递增。为什么不呢?
我在Chrome和Firefox中观察到了此行为。它似乎只有在有相邻的换行符时才会发生。
Tomalak下面说将模式更改为/^(.+)$/gm
会导致表达式不被卡住,但空行会被忽略。这可以改为仍然匹配线?感谢您的回答Tomalak!
使用以下模式并使用组1适用于我能想到的所有字符串。再次感谢Tomalak。
/^(.*)((\r\n|\r|\n)|$)/gm
上一个模式返回空白行。但是,如果您不关心空白行,Tomalak会给出以下解决方案,我认为该解决方案更清晰。
/^(.*)[\r\n]*/gm
前两个解决方案都停留在尾随换行符上,因此您必须将它们剥离或手动递增lastIndex
。
我在Flagrant Badassery找到了一篇很好的文章,详细介绍了lastIndex
的跨浏览器问题。除了令人敬畏的博客名称,这篇文章让我对这个问题有了更深入的了解,并提供了一个很好的跨浏览器解决方案。解决方案如下:
var rx = /^/gm,
tx = 'A\nB\nC',
m;
while(m = rx.exec(tx)){
if(!m[0].length && rx.lastIndex > m.index){
--rx.lastIndex;
}
foo();
if(!m[0].length){
++rx.lastIndex;
}
}
答案 0 :(得分:7)
问题在于
中的点^(.*)$
与换行符没有匹配,但是您使用"m"
开关将"^"
和"$"
锚定到新的换行符。这意味着"\n"
和"\n"
之间的“无”可以与"(.*)"
成功匹配。
由于此匹配的宽度为零,因此lastIndex
属性无法前进。尝试:
^(.+)$
编辑:要匹配空白行,请执行以下操作:
^(.*)\n? // remove all \r characters beforehand
或
^(.*)(?:\r\n|\n\r|\n|\r)? // all possible CR/LF combinations, but *once* at most
...然后去比赛组1。
答案 1 :(得分:2)
lastIndex的问题在于遵循该字母标准的JavaScript实现将其设置为匹配后的下一个字符的偏移量。对于像你这样的正则表达式,它允许零长度匹配,因此当找到零长度匹配时,exec()将陷入无限循环。下一次匹配尝试将从相同的位置开始,在该位置找到相同的零长度匹配。
传统上,正则表达式引擎通过在找到零长度匹配时跳过一个字符来处理此问题。顺便说一下,Internet Explorer也是这样做的。
我过去在博客中详细介绍了这一点:Watch Out for Zero-Length Matches