为什么带有全局标志的RegExp会给出错误的结果?

当我使用全局标志和不区分大小写的标志时,这个正则expression式有什么问题? 查询是用户生成的input。 结果应该是[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")); 

RegExp对象跟踪发生匹配的lastIndex ,所以在后续的匹配中,它将从最后使用的索引开始,而不是从0开始。看一看:

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

如果您不想在每次testing后手动将lastIndex重置为0,只需删除g标志。

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

RegExp.prototype.exec(串)

对正则expression式执行string的正则expression式匹配,并返回包含匹配结果的Array对象;如果string不匹配,则返回null如下所示searchstringToString(string)以查找正则expression式模式:

  1. 设S是ToString(string)的值。
  2. 设长度为S的长度
  3. 让lastIndex是lastIndex属性的值。
  4. 让我成为ToInteger(lastIndex)的值。
  5. 如果全局属性是假的,让我= 0。
  6. 如果我<0或I>长度然后设置lastIndex为0,并返回null。
  7. 调用[[Match]],给它参数S和i。 如果[[Match]]返回失败,请转至步骤8; 否则让r为其状态结果并转到步骤10。
  8. 让我=我+ 1。
  9. 转到第6步。
  10. 设e是r的endIndex值。
  11. 如果全局属性为true,则将lastIndex设置为e。
  12. 设n是r的捕获数组的长度。 (这与15.10.2.1的NCapturingParens相同。)
  13. 返回具有以下属性的新数组:
    • index属性被设置为完整stringS中的匹配子string的位置。
    • input属性设置为S.
    • 长度属性设置为n + 1。
    • 0属性被设置为匹配的子string(即,在偏移i包含和偏移e独占之间的S的部分)。
    • 对于I> 0和I≤n的每个整数i,将名为ToString(i)的属性设置为r的capture数组的第i个元素。

您正在使用一个RegExp对象并多次执行它。 在每个连续的执行过程中,从最后一个匹配索引继续。

您需要“重置”正则expression式,从每个执行开始之前开始:

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

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

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

RegExp.prototype.test更新正则expression式的lastIndex属性,以便每个testing将从最后一个停止的地方开始。 我build议使用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')); 

删除全球克旗将解决您的问题。

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

应该

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

使用/ g标志告诉它在命中后继续search。

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

在你第一次search之前:

 myRegex.lastIndex //is 0 

第一次search后

 myRegex.lastIndex //is 8 

删除g,并在每次调用exec()后退出search。