开发者问题收集

如何在 JavaScript 正则表达式中访问匹配的组?

2009-01-11
1089342

我想使用 正则表达式 匹配字符串的一部分,然后访问带括号的子字符串:

var myString = "something format_abc"; // I want "abc"

var arr = /(?:^|\s)format_(.*?)(?:\s|$)/.exec(myString);

console.log(arr); // Prints: [" format_abc", "abc"] .. so far so good.
console.log(arr[1]); // Prints: undefined  (???)
console.log(arr[0]); // Prints: format_undefined (!!!)

我做错了什么?


我发现上面的正则表达式代码没有任何问题:我测试的实际字符串是这样的:

"date format_%A"

报告“%A”未定义似乎是一个非常奇怪的行为,但它与这个问题没有直接关系,所以我开了一个新的问题, 为什么匹配的子字符串在JavaScript?


问题是 console.logprintf 语句一样接受其参数,并且由于我正在记录的字符串 ( "%A" ) 具有特殊值,因此它试图找到下一个参数的值。

3个回答

更新:2019-09-10

对多个匹配进行迭代的旧方法不太直观。这导致了 String.prototype.matchAll 方法的提出。此新方法位于 ECMAScript 2020 规范 中。它为我们提供了一个干净的 API 并解决了多个问题。自 Chrome 73+ / Node 12+ 和 Firefox 67+ 以来,它已出现在主流浏览器和 JS 引擎中。

该方法返回一个迭代器,使用方式如下:

const string = "something format_abc";
const regexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
const matches = string.matchAll(regexp);
    
for (const match of matches) {
  console.log(match);
  console.log(match.index)
}

由于它返回一个迭代器,我们可以说它是惰性的,这在处理特别大量的捕获组或非常大的字符串时很有用。但是如果你需要,可以使用 spread 语法 Array.from 方法轻松地将结果转换为数组:

function getFirstGroup(regexp, str) {
  const array = [...str.matchAll(regexp)];
  return array.map(m => m[1]);
}

// or:
function getFirstGroup(regexp, str) {
  return Array.from(str.matchAll(regexp), m => m[1]);
}

与此同时,虽然此提案得到了更广泛的支持,但你可以使用 官方 shim 包

此外,该方法的内部工作原理很简单。使用生成器函数的等效实现如下:

function* matchAll(str, regexp) {
  const flags = regexp.global ? regexp.flags : regexp.flags + "g";
  const re = new RegExp(regexp, flags);
  let match;
  while (match = re.exec(str)) {
    yield match;
  }
}

创建原始正则表达式的副本;这是为了避免在进行多个匹配时由于 lastIndex 属性的变异而产生的副作用。

此外,我们需要确保正则表达式具有 global 标志以避免无限循环。

我也很高兴看到这个 StackOverflow 问题在 提案讨论 中被引用。

原始答案

您可以像这样访问捕获组:

var myString = "something format_abc";
var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
var myRegexp = new RegExp("(?:^|\\s)format_(.*?)(?:\\s|$)", "g");
var matches = myRegexp.exec(myString);
console.log(matches[1]); // abc

如果有多个匹配项,您可以对它们进行迭代:

var myString = "something format_abc";
var myRegexp = new RegExp("(?:^|\\s)format_(.*?)(?:\\s|$)", "g");
match = myRegexp.exec(myString);
while (match != null) {
  // matched text: match[0]
  // match start: match.index
  // capturing group n: match[n]
  console.log(match[0])
  match = myRegexp.exec(myString);
}
Christian C. Salvadó
2009-01-11

您可以使用以下方法来获取每个匹配项的第 n 个捕获组:

function getMatches(string, regex, index) {
  index || (index = 1); // default to the first capturing group
  var matches = [];
  var match;
  while (match = regex.exec(string)) {
    matches.push(match[index]);
  }
  return matches;
}


// Example :
var myString = 'something format_abc something format_def something format_ghi';
var myRegEx = /(?:^|\s)format_(.*?)(?:\s|$)/g;

// Get an array containing the first capturing group for every match
var matches = getMatches(myString, myRegEx, 1);

// Log results
document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
console.log(matches);
Mathias Bynens
2013-01-08
var myString = "something format_abc";
var arr = myString.match(/\bformat_(.*?)\b/);
console.log(arr[0] + " " + arr[1]);

\b 并不完全相同。(它适用于 --format_foo/ ,但不适用于 format_a_b )但我想展示您的表达式的替代方案,这很好。当然, match 调用是最重要的。

PhiLho
2009-01-11