为什么带有全局标志的正则表达式会给出错误的结果?

Why does a RegExp with global flag give wrong results?

提问人:about 提问时间:10/5/2009 最后编辑:zb226about 更新时间:7/27/2022 访问量:63316

问:

当我使用全局标志和不区分大小写标志时,这个正则表达式有什么问题?查询是用户生成的输入。结果应为 [true, true]。

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
result.push(re.test('Foo Bar'));
// result will be [true, false]

var reg = /^a$/g;
for(i = 0; i++ < 10;)
   console.log(reg.test("a"));

JavaScript 正则表达式

评论

92赞 bobince 10/6/2009
欢迎来到 JavaScript 中 RegExp 的众多陷阱之一。它具有我见过的最糟糕的正则表达式处理接口之一,充满了奇怪的副作用和晦涩难懂的警告。您通常希望使用正则表达式执行的大多数常见任务都很难正确拼写。
0赞 about 10/6/2009
XRegExp 看起来是一个不错的选择。xregexp.com
0赞 Prestaul 8/29/2014
也可以在这里看到答案:stackoverflow.com/questions/604860/......
0赞 thdoan 10/11/2018
如果可以的话,一种解决方案是直接使用正则表达式文字,而不是将其保存到 .re

答:

86赞 Roatin Marth 10/5/2009 #1

您正在使用单个对象并多次执行它。在每次连续执行时,它从上次匹配索引继续。RegExp

您需要在每次执行之前将正则表达式“重置”为从头开始:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));
// result is now [true, true]

话虽如此,每次创建一个新的 RegExp 对象可能更具可读性(开销很小,因为无论如何都会缓存 RegExp):

result.push((/Foo B/gi).test(stringA));
result.push((/Foo B/gi).test(stringB));

评论

4赞 melpomene 9/20/2019
或者干脆不使用标志。g
42赞 James 10/5/2009 #2

RegExp.prototype.test 更新正则表达式的 lastIndex 属性,以便每个测试都将从最后一个测试停止的地方开始。我建议使用 String.prototype.match,因为它不会更新 lastIndex 属性:

!!'Foo Bar'.match(re); // -> true
!!'Foo Bar'.match(re); // -> true

注意: !! 将其转换为布尔值,然后反转布尔值以反映结果。

或者,您可以重置 lastIndex 属性:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));
455赞 Ionuț G. Stan 10/5/2009 #3

带有标志的对象会跟踪发生匹配的 lastIndex,因此在后续匹配中,它将从上次使用的索引开始,而不是从 0 开始。看一看:RegExpg

var query = 'Foo B';
var re = new RegExp(query, 'gi');
console.log(re.lastIndex);

console.log(re.test('Foo Bar'));
console.log(re.lastIndex);

console.log(re.test('Foo Bar'));
console.log(re.lastIndex);

如果您不想在每次测试后手动重置为 0,只需删除该标志即可。lastIndexg

以下是规范规定的算法(第 15.10.6.2 节):

RegExp.prototype.exec(字符串)

执行 字符串的正则表达式匹配 针对正则表达式和 返回一个 Array 对象,其中包含 匹配结果,如果 string did not match 字符串 ToString(string) 用于搜索 正则表达式的出现 模式如下:

  1. R 是这个 RexExp 对象。
  2. S 是 ToString(string) 的值。
  3. lengthS 的长度。
  4. 设 lastIndex 是 R 上 lastIndex 属性的值。
  5. 设 i 是 ToInteger(lastIndex) 的值。
  6. 如果全局属性为 false,则 i = 0。
  7. 如果 i < 0 或 i >长度,则将 RlastIndex 属性设置为 0 并返回 null。
  8. 调用 [[Match]],为其提供参数 S 和 i。如果 [[匹配]] 返回失败,转到步骤 9; 否则设 r 为其状态结果 ,然后转到步骤 10。
  9. 设 i = i+1。
  10. 转到步骤 7。
  11. 设 e 是 r 的 endIndex 值。
  12. 如果 global 属性为 true,则将 RlastIndex 属性设置为 e。
  13. 设 n 是 r 的捕获数组的长度。(这是一样的 值为 15.10.2.1 NCapturingParens.)
  14. 返回具有以下属性的新数组:
  • 指数 属性设置为 完整中匹配的子字符串 字符串 S。
  • 设置了 input 属性 到 S。
  • length 属性设置为 n + 1。
  • 0 属性设置为 匹配的子字符串(即 S 介于偏移量 i 和 偏移 e 独家)。
  • 对于每个 整数 i,使得 i > 0,我≤ n, 将名为 ToString(i) 的属性设置为 R 的第 i 个元素捕获数组。

评论

127赞 Retsam 8/23/2013
这就像这里的 Hitchhiker's Guide to the Galaxy API 设计。“你掉进去的那个陷阱已经完美地记录在规范中好几年了,如果你只是费心去检查的话”
5赞 Doin 1/14/2014
Firefox 的粘性标志根本没有做你暗示的事情。相反,它的作用就好像在正则表达式的开头有一个 ^,只是这个 ^ 与当前字符串位置 (lastIndex) 匹配,而不是字符串的开头。您实际上是在测试正则表达式是否匹配“right here”而不是“anywhere after lastIndex”。请参阅您提供的链接!
1赞 Prestaul 8/29/2014
这个答案的开场白并不准确。您突出显示了规范的第 3 步,该步骤什么也没说。的实际影响在步骤 5、6 和 11 中。只有在设置了全局标志时,您的开场白才为真。lastIndex
1赞 Ionuț G. Stan 8/29/2014
@Prestaul是的,你说得对,它没有提到全球国旗。由于问题的框架方式,这可能是(不记得我当时的想法)隐含的。您可以随意编辑或删除答案并链接到您的答案。另外,让我向你保证,你比我好。享受!
0赞 Prestaul 8/30/2014
@IonuțG.斯坦,对不起,如果我之前的评论看起来很有攻击性,那不是我的本意。我现在无法编辑它,但我不是想大喊大叫,只是为了引起人们对我评论的要点的注意。我的错!
18赞 user2572074 11/13/2013 #4

删除全局标志将解决您的问题。g

var re = new RegExp(query, 'gi');

应该是

var re = new RegExp(query, 'i');
1赞 Scott Schlechtleitner 9/21/2017 #5

使用 /g 标志会告知它在命中后继续搜索。

如果匹配成功,exec() 方法将返回一个数组并更新正则表达式对象的属性。

在您第一次搜索之前:

myRegex.lastIndex
//is 0

第一次搜索后

myRegex.lastIndex
//is 8

删除 g,它会在每次调用 exec() 后退出搜索。

评论

0赞 melpomene 9/20/2019
OP 未使用 .exec
-1赞 Chelmite 6/29/2018 #6

我有这个功能:

function parseDevName(name) {
  var re = /^([^-]+)-([^-]+)-([^-]+)$/g;
  var match = re.exec(name);
  return match.slice(1,4);
}

var rv = parseDevName("BR-H-01");
rv = parseDevName("BR-H-01");

第一个调用有效。 第二个调用没有。该操作抱怨 null 值。我认为这是因为.这很奇怪,因为我希望每次调用函数时都会分配一个新的函数,而不是在函数的多个调用之间共享。slicere.lastIndexRegExp

当我把它改成:

var re = new RegExp('^([^-]+)-([^-]+)-([^-]+)$', 'g');

然后我没有得到滞留效应。它的工作方式与我所期望的一样。lastIndex

2赞 Ashish 1/29/2020 #7

您需要设置 re.lastIndex = 0,因为使用 g 标志正则表达式跟踪上次发生的匹配,因此 test 不会去测试相同的字符串,因为您需要执行 re.lastIndex = 0

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
re.lastIndex=0;
result.push(re.test('Foo Bar'));

console.log(result)