33

当我跑

/(a)/g.exec('a a a ').length

我明白了

2

但我认为它应该返回

3

a因为字符串中有 3 个,而不是 2 个!

这是为什么?

我希望能够在 RegEx 中搜索所有出现的字符串并对其进行迭代。

FWIW:我正在使用 node.js

4

9 回答 9

42

exec()仅返回第一个匹配的捕获集,而不是您期望的匹配集。所以你真正看到的是$0(整个比赛,“a”)和$1(第一次捕获)——即一个长度为 2 的数组。 同时被设计为你可以再次exec()调用它来获取一场比赛的捕获. 来自MDN

如果您的正则表达式使用“g”标志,您可以多次使用 exec 方法在同一字符串中查找连续匹配项。当您这样做时,搜索将从正则表达式的 lastIndex 属性指定的 str 的子字符串开始(测试也将推进 lastIndex 属性)。

于 2012-06-30T00:04:44.423 回答
34

您可以match改用:

'a a a'.match(/(a)/g).length  // outputs: 3
于 2012-06-30T00:03:08.843 回答
18

while循环可以帮助你

x = 'a a a a';
y = new RegExp(/a/g);
while(null != (z=y.exec(x))) {
   console.log(z);     // output: object
   console.log(z[0]);  // ouput: "a"
}

如果你添加计数器,那么你会得到它的长度。

x = 'a a a a';
counter = 0;
y = new RegExp(/a/g);
while(null != (z=y.exec(x))) {
   console.log(z);     // output: object
   console.log(z[0]);  // output: "a"
   counter++;
}
console.log(counter);  // output: 4

这是非常安全的,即使它没有找到任何匹配项,它也会退出并且计数器将为 0

主要目的是说明如何使用 RegExp 循环并从相同匹配的 RegExp 字符串中获取所有值

于 2018-05-14T13:51:02.213 回答
8

您只匹配第一个 a。长度为 2 的原因是它正在查找第一个匹配项和第一个匹配项的括号组部分。在您的情况下,它们是相同的。

考虑这个例子。

var a = /b(a)/g.exec('ba ba ba ');
alert(a);

它输出ba, a. 数组长度仍然是 2,但更明显的是发生了什么。"ba" 是完全匹配。 a是括号内的第一个分组匹配。

MDN 文档支持这一点——只返回第一个匹配项和包含的组。要查找所有匹配项,您将使用 mVChr 所述的 match()。

于 2012-06-30T00:07:52.810 回答
4

代码:

alert('a a a'.match(/(a)/g).length);

输出:

3
于 2012-06-30T00:11:10.990 回答
2

regexp.exec(str)返回第一个匹配项或整个匹配项和第一个捕获(何时re = /(a)/g;),如其他答案中所述

const str = 'a a a a a a a a a a a a a';
const re = /a/g;

const result = re.exec(str);
console.log(result);

但它也会记住它在regexp.lastIndex属性中的位置。

下一个调用开始搜索regexp.lastIndex并返回下一个匹配项。

如果没有更多匹配项,则regexp.exec返回 null 并regexp.lastIndex设置为 0。

const str = 'a a a';
const re = /a/g;

const a = re.exec(str);
console.log('match : ', a, ' found at : ', re.lastIndex);

const b = re.exec(str);
console.log('match : ', b, ' found at : ', re.lastIndex);

const c = re.exec(str);
console.log('match : ', c, ' found at : ', re.lastIndex);

const d = re.exec(str);
console.log('match : ', d, ' found at : ', re.lastIndex);

const e = re.exec(str);
console.log('match : ', e, ' found at : ', re.lastIndex);

这就是为什么你可以使用一个while循环,当比赛结束时会停止null

const str = 'a a a';
const re = /a/g;

while(match = re.exec(str)){
  console.log(match, ' found at : ', match.index); 
}

于 2019-03-09T00:09:02.830 回答
1

已经有几个答案,但不必要地复杂。对结果的身份检查过多,因为它始终是数组或null.

let text = `How much wood would a woodchuck chuck if a woodchuck could chuck wood?`;
let re = /wood/g;
let lastMatch;

while (lastMatch = re.exec(text)) {
  console.log(lastMatch);
  console.log(re.lastIndex);

  // Avoid infinite loop
  if(!re.global) break;
}

您可以将无限循环保护移动到条件表达式中。

while (re.global && (lastMatch = re.exec(text))) {
 console.log(lastMatch);
 console.log(re.lastIndex);
}
于 2021-08-15T14:19:50.967 回答
0

封装成一个效用函数:

const regexExecAll = (str: string, regex: RegExp) => {
  let lastMatch: RegExpExecArray | null;
  const matches: RegExpExecArray[] = [];

  while ((lastMatch = regex.exec(str))) {
    matches.push(lastMatch);

    if (!regex.global) break;
  }

  return matches;
};

用法:

const matches = regexExecAll("a a a", /(a)/g);

console.log(matches);

输出:

[
  [ 'a', 'a', index: 0, input: 'a a a', groups: undefined ],
  [ 'a', 'a', index: 2, input: 'a a a', groups: undefined ],
  [ 'a', 'a', index: 4, input: 'a a a', groups: undefined ]
]
于 2021-10-01T15:27:05.263 回答
0

对于您的示例,.match()是您的最佳选择。但是,如果您确实需要子组,则可以创建一个生成器函数。

function* execAll(str, regex) {
  if (!regex.global) {
    console.error('RegExp must have the global flag to retrieve multiple results.');
  }

  let match;
  while (match = regex.exec(str)) {
    yield match;
  }
}

const matches = execAll('a abbbbb no match ab', /\b(a)(b+)?\b/g);
for (const match of matches) {
  console.log(JSON.stringify(match));
  let otherProps = {};
  for (const [key, value] of Object.entries(match)) {
    if (isNaN(Number(key))) {
      otherProps[key] = value;
    }
  }
  
  console.log(otherProps);
}

虽然大多数 JS 程序员认为污染原型是不好的做法,但您也可以将其添加到RegExp.prototype.

if (RegExp.prototype.hasOwnProperty('execAll')) {
  console.error('RegExp prototype already includes a value for execAll.  Not overwriting it.');
} else {
  RegExp.prototype.execAll = 
    RegExp.prototype = function* execAll(str) {
      if (!this.global) {
        console.error('RegExp must have the global flag to retrieve multiple results.');
      }

      let match;
      while (match = this.exec(str)) {
        yield match;
      }
    };
}

const matches = /\b(a)(b+)?\b/g.execAll('a abbbbb no match ab');
console.log(Array.from(matches));

于 2021-02-01T17:47:29.300 回答