讲解

反向引用让「后面的内容与前面捕获的内容相同」成为可表达的条件:\1 引用第 1 个捕获组当时匹配到的确切文本(不是模式本身)。比如 /(\w+)\s+\1/ 匹配连续重复的单词——第二个词必须和第一个词一模一样。这是正则超越「正则语言」理论能力的一个特性,也带来了额外的匹配成本。

经典应用是匹配成对出现的符号:引号配对 /(["']).*?\1/ 保证首尾引号一致(要么都是双引号要么都是单引号),不会出现前双后单的错位。命名分组的反向引用写作 \k,语义相同但不怕组编号变动。

要注意两处语法不通用:在正则模式内部引用用 \1,在 replace 的替换串里引用要用 $1——两者不能互换。/\1/ 写进替换串会得到字面的控制字符,$1 写进模式里则是「$ 锚点后面跟个 1」的荒诞组合。另外,如果引用的组参与了分支但未实际匹配,\1 匹配空串,这个边界行为各引擎一致但容易出乎意料。

示例

// 检测连续重复的单词:\1 必须和第 1 组捕获的文本完全相同
const re = /\b(\w+)\s+\1\b/;
console.assert(re.test('this is is a test'));
console.assert(re.test('very very good'));
console.assert(re.test('this is a test') === false);
console.assert(re.test('cat category') === false); // cat 与 category 不相同

成对引号与命名反向引用:

// \1 保证闭合引号与开头引号一致
const quote = /(["'])(.*?)\1/;
const m1 = '他说 "你好" 之后走了'.match(quote);
console.assert(m1[2] === '你好');
console.assert(m1[1] === '"');
const m2 = "单引号 '也行' 的例子".match(quote);
console.assert(m2[2] === '也行');

// 命名反向引用 \k<name>
const pair = /(?<q>["'])(.*?)\k<q>/;
console.assert(pair.test('"双引号"'));
console.assert(pair.test("'单引号'"));
console.assert(pair.test('"前双后单\'') === false); // 引号不一致就匹配不到内容

模式内用 \1,替换串里用 $1,别搞混:

// replace 里引用捕获组用 $1 $2
const swapped = '张三 18'.replace(/(\S+) (\d+)/, '$2 岁的 $1');
console.assert(swapped === '18 岁的 张三');

// 对比:替换串里的 \1 不会引用捕获组(得到的是字面控制字符)
const wrong = 'ab'.replace(/(a)(b)/, '\1');
console.assert(wrong !== 'a'); // 证明 \1 在替换串里不是反向引用

常见坑

  • 在替换串里写 \1:替换串的引用语法是 $1(命名组 $),\1 只用于模式内部。两边写反都不会报错,只会静默出怪结果。
  • 组未实际匹配时的行为:/(a)|b/ 匹配 b 时第 1 组未参与,此时 \1 匹配空串——/(?:(a)|b)\1/ 能匹配 b。依赖组必定有值时先想清楚分支。
  • 滥用反向引用导致性能问题:反向引用让匹配不再是纯粹的有限自动机能处理的问题,配合量词和分支时回溯成本会明显上升。
  • 数错编号:\2 指第二个左括号的组,(?:...) 不占编号但 (?...) 占。组多时用命名引用 \k 最保险。

小结

\1 复用前面捕获的文本,适合重复检测与成对匹配;模式内 \1、替换串 $1,命名组用 \k。下一节系统整理转义规则。