分组和反向引用

分组将多个模式作为一个整体进行分组,而捕获组在使用正则表达式模式匹配字符串时提供额外的子匹配信息。反向引用引用同一正则表达式中先前捕获的组。

试一试

// Groups
const imageDescription = "This image has a resolution of 1440×900 pixels.";
const regexpSize = /(\d+)×(\d+)/;
const match = imageDescription.match(regexpSize);
console.log(`Width: ${match[1]} / Height: ${match[2]}.`);
// Expected output: "Width: 1440 / Height: 900."

// Backreferences
const findDuplicates = "foo foo bar";
const regex = /\b(\w+)\s+\1\b/g;
console.log(findDuplicates.match(regex));
// Expected output: Array ["foo foo"]

类型

字符 含义
(x)

捕获组: 匹配 x 并记住匹配。例如,/(foo)/ 匹配并记住 "foo" 在 "foo bar" 中。

一个正则表达式可能包含多个捕获组。在结果中,捕获组的匹配通常在一个数组中,其成员的顺序与捕获组中左括号的顺序相同。这通常就是捕获组本身的顺序。当捕获组嵌套时,这变得很重要。可以使用结果元素的索引 ([1], …, [n]) 或预定义 RegExp 对象的属性 ($1, …, $9) 来访问匹配。

捕获组会带来性能开销。如果您不需要记住匹配的子字符串,请优先使用非捕获括号(见下文)。

如果设置了 /.../g 标志,String.prototype.match() 不会返回组。但是,您仍然可以使用 String.prototype.matchAll() 来获取所有匹配。

(?<Name>x)

命名捕获组: 匹配 "x" 并将其存储在返回匹配的 groups 属性中,名称由 <Name> 指定。组名需要尖括号 (<>)。

例如,要从电话号码中提取美国区号,我们可以使用 /\((?<area>\d\d\d)\)/。结果数字将出现在 matches.groups.area 下。

(?:x)

非捕获组: 匹配 "x" 但不记住匹配。无法从结果数组的元素 ([1], …, [n]) 或预定义 RegExp 对象的属性 ($1, …, $9) 中召回匹配的子字符串。

(?flags:x), (?:flags-flags:x)

修饰符: 仅对封闭模式启用或禁用指定标志。修饰符中只能使用 ims 标志。

\n

反向引用: 其中 "n" 是一个正整数。匹配正则表达式中第 n 个捕获组(从左括号开始计数)匹配的相同子字符串。例如,/apple(,)\sorange\1/ 匹配 "apple, orange, cherry, peach" 中的 "apple, orange,"。

\k<Name>

命名反向引用: 反向引用到由 <Name> 指定的命名捕获组最后匹配的子字符串。

例如,/(?<title>\w+), yes \k<title>/ 匹配 "Do you copy? Sir, yes Sir!" 中的 "Sir, yes Sir"。

注意: 这里的 \k 字面上表示命名捕获组反向引用的开始。

示例

使用分组

在此示例中,我们通过使用捕获组来记住两个单词,从而匹配结构化格式中的两个单词。 \w+ 匹配一个或多个单词字符,括号 () 创建一个捕获组。 g 标志用于匹配所有出现。

js
const personList = `First_Name: John, Last_Name: Doe
First_Name: Jane, Last_Name: Smith`;

const regexpNames = /First_Name: (\w+), Last_Name: (\w+)/g;
for (const match of personList.matchAll(regexpNames)) {
  console.log(`Hello ${match[1]} ${match[2]}`);
}

捕获组参考中查看更多示例。

使用命名组

此示例与上面相同,但我们使用命名捕获组来记住匹配的单词。这样,我们可以通过它们的含义访问匹配的单词。

js
const personList = `First_Name: John, Last_Name: Doe
First_Name: Jane, Last_Name: Smith`;

const regexpNames =
  /First_Name: (?<firstName>\w+), Last_Name: (?<lastName>\w+)/g;
for (const match of personList.matchAll(regexpNames)) {
  console.log(`Hello ${match.groups.firstName} ${match.groups.lastName}`);
}

命名捕获组参考中查看更多示例。

使用分组和反向引用

在此示例中,我们首先使用 ['"] 匹配单引号或双引号字符,记住它,然后使用 .*? 匹配任意数量的字符 (*? 是一个非贪婪量词),直到我们再次使用 \1 匹配记住的引号字符。 \1 是对第一个捕获组的反向引用,它匹配相同类型的引号。因此,结果将是两个字符串: "'"'"'

js
const quote = `Single quote "'" and double quote '"'`;
const regexpQuotes = /(['"]).*?\1/g;
for (const match of quote.matchAll(regexpQuotes)) {
  console.log(match[0]);
}

反向引用参考中查看更多示例。

使用分组和匹配索引

通过提供 d 标志,将返回每个捕获组的索引。如果您将每个匹配的组与原始文本关联起来,例如提供编译器诊断,这尤其有用。

js
const code = `function add(x, y) {
  return x + y;
}`;
const functionRegexp =
  /(function\s+)(?<name>[$_\p{ID_Start}][$\p{ID_Continue}]*)/du;
const match = functionRegexp.exec(code);
const lines = code.split("\n");
lines.splice(
  1,
  0,
  " ".repeat(match.indices[1][1] - match.indices[1][0]) +
    "^".repeat(match.indices.groups.name[1] - match.indices.groups.name[0]),
);
console.log(lines.join("\n"));
// function add(x, y) {
//          ^^^
//   return x + y;
// }

另见