为什么带有全局标志的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"));

58105 次浏览

你正在使用一个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));

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标志的RegExp对象跟踪发生匹配的lastIndex,因此在后续匹配时,它将从上次使用的索引开始,而不是0。来看看:

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

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

下面是规范规定的算法(章节15.10.6.2):

RegExp.prototype.exec(字符串)

< p >执行 字符串的正则表达式匹配 针对正则表达式和 返回一个数组对象 匹配的结果,或null string did not match字符串 ToString(string)用于搜索 正则表达式的出现 模式如下:

  1. R为这个RexExp对象。
  2. 年代为ToString(string)的值。
  3. 长度年代的长度。
  4. 设lastIndex为R上的lastIndex属性的值。
  5. 设i为ToInteger(lastIndex)的值。
  6. 如果全局属性为false,令i = 0。
  7. 如果i <0或I >然后将RlastIndex属性设置为0并返回null。
  8. 调用[[Match]],给出参数S和i。If [[Match]] 返回失败,执行步骤9; 否则让r为它的State结果
  9. .执行步骤10
  10. 令i = i+1。
  11. 执行步骤7。
  12. 设e为r的endIndex值。
  13. 如果global属性为true,则将RlastIndex属性设置为e。
  14. 设n为r的capture数组的长度。(这是一样的 值为15.10.2.1 s 李NCapturingParens)。< / >
  15. 返回一个具有以下属性的新数组:
    <李>索引 属性的位置 匹配的子字符串 李字符串s . < / >
  • 设置输入属性 李s . < / >
  • length属性设置为 N + 1,
  • 0属性被设置为 匹配的子字符串(即 S在偏移量i包含和 偏移量除外)。
  • <李> 整数I,满足I >0且I≤n, 设置名为ToString(i)的属性为 r的第i个元素捕获数组。

移除全局g标志将解决你的问题。

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

应该是

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

函数是:

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的延迟效果。正如我所期望的那样。

你需要设置re.lastIndex = 0,因为带有g标志的regex会跟踪最近发生的匹配,所以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)