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

问题描述:

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

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"));

+36

欢迎来到JavaScript中RegExp的众多陷阱之一。它是我见过的正则表达式处理的最糟糕的接口之一,充满了奇怪的副作用和隐晦的警告。你通常想用正则表达式处理的大多数常见任务很难拼写正确。 – bobince 2009-10-05 16:07:07

+0

XRegExp看起来很不错。 http://xregexp.com/ – about 2009-10-05 18:49:41

+0

在这里也可以看到答案:http://*.com/questions/604860/interesting-test-of-javascript-regexp – Prestaul 2014-08-28 18:40:59

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')); 

如果你不想每次测试后,手动复位lastIndex为0,只是删除g标志。

下面是该规格规定(第15.10.6.2)算法:

RegExp.prototype。EXEC(字符串)

执行 串 的正则表达式匹配针对正则表达式和 返回包含 结果匹配的,或空如果 串不匹配的字符串 的ToString(Array对象串)中搜索一个 发生正则表达式的 图案如下:

  1. 设S是的ToString(字符串的值)。
  2. 设长度为S.的长度。
  3. 让lastIndex为lastIndex属性的值。
  4. 让我成为ToInteger(lastIndex)的值。
  5. 如果全局属性为false,则让i = 0。
  6. 如果I < 0或I>长度,则将lastIndex设置为0并返回null。
  7. 调用[[Match]],给它参数S和i。如果[[匹配]] 返回失败,请转至步骤8; 否则让r为其状态结果 并转到步骤10.
  8. 让i = i + 1。
  9. 转到步骤6.
  10. 设e是r的endIndex值。
  11. 如果全局属性为true,则将lastIndex设置为e。
  12. 设n是r的捕获数组长度。 (这是相同的 值15.10.2.1的 NCapturingParens。)
  13. 返回一个新的数组具有以下属性:
    • 索引 属性被设置为 匹配的子的位置内的完整 串S.
    • 输入属性设置 到S.
    • 长度属性设置为 N + 1
    • 的0 prope rty被设置为 匹配的子字符串(即,在偏移量i包括在内的 的部分和 偏移e除外)。
    • 对于每个 整数i,使得I> 0和I≤n, 将名为ToString(i)的属性设置为r的捕获数组的第i个元素。
+39

这就像这里的银河API设计的Hitchhiker指南。 “如果你只是想检查” – Retsam 2013-08-22 19:54:56

+4

Firefox的粘性标志并不能完全符合你的意思,那么你陷入的这个陷阱已经在规范中被完全记录了好几年。相反,它的行为好像在正则表达式的开始处有一个^,除了这个匹配* current *字符串的位置(lastIndex)而不是字符串的开头。如果正则表达式匹配“right here”而不是“lastIndex后的任何位置”,则可以有效地进行测试。查看您提供的链接! – Doin 2014-01-14 12:15:53

+0

这个答案的开头语句是不准确的。你突出显示了什么都没说的规格的第3步。 'lastIndex'的实际影响是在步骤5,6和11中。如果设置了全局标志,则您的开始语句仅为真。 – Prestaul 2014-08-28 18:38:25

您使用的是单RegExp对象,并多次执行它。在每次连续执行时,它将从最后一个匹配索引继续。

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

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

说了这么多,可能更具可读性每次(开销最小为创建一个新的RegExp对象正则表达式是无论如何缓存):

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

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')); 

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

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

应该

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

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

If the match succeeds, the exec() method returns an array and updates properties of the regular expression object.

您的第一个搜索之前:

myRegex.lastIndex 
//is 0 

第一搜索

myRegex.lastIndex 
//is 8 

后取出G和它在每次通话后退出搜索给exec()。