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

IT技术 javascript regex
2020-12-22 16:16:56

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

    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.log它像一个printf语句一样接受它的参数,并且由于我正在记录的字符串 ( "%A") 有一个特殊的值,它试图找到下一个参数的值。

6个回答

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

var myString = "something format_abc";
var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
var myRegexp = new RegExp("(?:^|\s)format_(.*?)(?:\s|$)", "g");
var match = myRegexp.exec(myString);
console.log(match[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);
}

编辑:2019-09-10

如您所见,迭代多个匹配项的方式不是很直观。这导致了该String.prototype.matchAll方法的提议这种新方法预计将在ECMAScript 2020 规范中发布它为我们提供了一个干净的 API 并解决了多个问题。它已经开始登陆主流浏览器和 JS 引擎,如Chrome 73+ / Node 12+和 Firefox 67+。

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

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

当它返回一个迭代器时,我们可以说它是惰性的,这在处理特别大量的捕获组或非常大的字符串时很有用。但是如果需要,可以使用扩展语法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在进行多次匹配时由于属性突变而产生的副作用

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

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

需要注意的是,第 0 个索引是整个匹配项。const [_, group1, group2] = myRegex.exec(myStr);我的模式也是如此
2021-02-10 16:16:56
为什么做上述而不是:var match = myString.match(myRegexp); // alert(match[1])
2021-02-13 16:16:56
@ianaz:我不相信这是真的?http://jsfiddle.net/weEg9/似乎至少适用于 Chrome。
2021-03-01 16:16:56
不需要显式的“new RegExp”,但是除非指定 /g,否则将发生无限循环
2021-03-03 16:16:56
+1 请注意,在第二个示例中,您应该使用 RegExp 对象(不仅是“/myregexp/”),因为它保留了对象中的 lastIndex 值。不使用 Regexp 对象,它将无限迭代
2021-03-09 16:16:56

下面是一种方法,您可以使用它来获取每个匹配项的第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);

这是一个远远优于其他答案的答案,因为它正确地显示了所有匹配项的迭代,而不是只得到一个匹配项。
2021-03-05 16:16:56

var myString = "something format_abc";
var arr = myString.match(/\bformat_(.*?)\b/);
console.log(arr[0] + " " + arr[1]);

\b不是完全一样的东西。(它适用于--format_foo/,但不适用于format_a_b)但我想展示一种替代你的表达方式,这很好。当然,match电话是最重要的。

@BFHonestly,我format_a_b在 6 年前添加了“不起作用”作为事后的想法,我不记得我在那里的意思...... :-) 我想这意味着“不能a捕捉”, IE。之后的第一个字母部分format_
2021-02-08 16:16:56
我想说 \b(--format_foo/}\b 不返回 "--format_foo/" 因为 "-" 和 "/" 不是 \word 字符。但是 \b(format_a_b)\b 确实返回 "format_a_b “。对吗?我指的是你在圆括号中的文字声明。(没有投反对票!)
2021-02-20 16:16:56
恰恰相反。'\b' 分隔单词。字= '\w' = [a-zA-Z0-9_] 。“format_a_b”是一个词。
2021-02-25 16:16:56
请注意,g标志在这里很重要。如果将g标志添加到模式中,您将获得一组不考虑捕获组的匹配项。"a b c d".match(/(\w) (\w)/g);=>["a b", "c d"]但是"a b c d".match(/(\w) (\w)/);=> ["a b", "a", "b", index: 0, input: "a b c d", groups: undefined]
2021-03-05 16:16:56

最后但并非最不重要的一点是,我发现了一行对我来说效果很好的代码(JS ES6):

let reg = /#([\S]+)/igm; // Get hashtags.
let string = 'mi alegría es total! ✌🙌\n#fiestasdefindeaño #PadreHijo #buenosmomentos #france #paris';

let matches = (string.match(reg) || []).map(e => e.replace(reg, '$1'));
console.log(matches);

这将返回:

['fiestasdefindeaño', 'PadreHijo', 'buenosmomentos', 'france', 'paris']

关于上面的多匹配括号示例,我在没有得到我想要的东西后在这里寻找答案:

var matches = mystring.match(/(?:neededToMatchButNotWantedInResult)(matchWanted)/igm);

在查看了上面使用 while 和 .push() 的稍微复杂的函数调用之后,我突然意识到这个问题可以用 mystring.replace() 非常优雅地解决(替换不是重点,甚至还没有完成) ,第二个参数的 CLEAN 内置递归函数调用选项是!):

var yourstring = 'something format_abc something format_def something format_ghi';

var matches = [];
yourstring.replace(/format_([^\s]+)/igm, function(m, p1){ matches.push(p1); } );

在此之后,我认为我再也不会将 .match() 用于任何事情了。