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

时间:2009-10-05 15:32:01

标签: javascript regex

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

7 个答案:

答案 0 :(得分:299)

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(字符串)

     

执行   字符串的正则表达式匹配   反对正则表达和   返回一个包含的Array对象   匹配的结果,如果是,则为null   字符串不匹配字符串   搜索ToString(字符串)   正则表达式的出现   模式如下:

     
      
  1. 设S为ToString(string)的值。
  2.   
  3. 设长度为S的长度。
  4.   
  5. 让lastIndex为lastIndex属性的值。
  6.   
  7. 让我成为ToInteger(lastIndex)的值。
  8.   
  9. 如果全局属性为false,则让i = 0。
  10.   
  11. 如果我&lt; 0或I&gt;然后将lastIndex设置为0并返回null。
  12.   
  13. 调用[[Match]],给它参数S和i。如果[[匹配]]   返回失败,转到第8步;   否则让r成为州的结果   并转到第10步。
  14.   
  15. 设i = i + 1。
  16.   
  17. 转到第6步。
  18.   
  19. 设e为r的endIndex值。
  20.   
  21. 如果global属性为true,请将lastIndex设置为e。
  22.   
  23. 设n为r的捕获数组的长度。 (这是一样的   值为15.10.2.1   NCapturingParens。)
  24.   
  25. 返回具有以下属性的新数组:      
        
    • 指数   属性设置为的位置   完整的匹配子串   string S。
    •   
    • 设置输入属性   到S。
    •   
    • length属性设置为   n + 1。
    •   
    • 0属性设置为   匹配的子串(即。的一部分)   S在offset i包含和之间   抵消e独家)。
    •   
    • 每个人   整数i使得I> 0和I≤n,   将名为ToString(i)的属性设置为   r的第i个元素捕获数组。
    •   
  26.   

答案 1 :(得分:65)

您正在使用单个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));

答案 2 :(得分:34)

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

答案 3 :(得分:9)

删除全局g标记可以解决您的问题。

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

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

答案 4 :(得分:0)

使用/ 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()后退出搜索。

答案 5 :(得分:0)

我具有以下功能:

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

第一个呼叫有效。 第二个电话没有。 slice操作抱怨为空值。我认为这是由于re.lastIndex引起的。这很奇怪,因为我希望每次调用该函数时都会分配一个新的RegExp,并且不会在函数的多次调用之间共享。

当我将其更改为:

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

然后我没有得到lastIndex保持效果。它的工作符合我的预期。

答案 6 :(得分:0)

您需要设置re.lastIndex = 0,因为使用g标志regex跟踪发生的最后一次匹配,因此测试不会去测试相同的字符串,因为您需要这样做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)