命名捕获 vs 反向引用:一个共用语法,一个共用文本
regex 语法速览页把命名组和反向引用各一笔带过。它们常被混在一起,其实是两种不同的「共用」—— 分清楚,后面一堆困惑都解开了:
(?<x>…) = 共用 regex 语法。
给某个捕获组起个名字 x;这个名字只是 \1 \2 数字下标的可读别名,
指向「pattern 里的那个组」。同一个名字之后能在 pattern 内 (\k<x>)、替换串里 ($<x>)、
结果对象里 (m.namedGroups.x) 到处复用——复用的是这个引用 / 标签,不增加任何匹配能力。
\1 / \k<x> = 共用匹配到的字符串。
它在匹配时不是「再跑一遍那段 pattern」,而是要求之前那个组实际吃到的那串文本,原封不动再出现一次。
复用的是运行时捕获到的文本——这让正则超出了「正则语言」,引擎得切到回溯。
(\w+) \1 不是「一个词 + 空格 + 任意一个词」,而是「一个词 + 空格 + 同一个词」。
下面第二节那个对照框就在演示这件事。
命名捕获:一个名字,处处复用
把组起个名,匹配结果里就能按名字取字段(不用数 groups[3] 是第几个),替换时还能按名重排。
下面同时列出编号组 groups[] 与命名组 namedGroups{}——一眼看出
namedGroups.y 就是 groups[1],名字纯属别名。开 d 标志还能看到 indices.groups(各命名组的区间)。
d = 输出 indices · g = 替换全部
模板里
$<name> 按名插回
命中高亮
y 在四处指代同一个组:
定义(?<y>\d{4})-(?<m>\d{2})
pattern 内\k<y> ←反向引用 (见第 3 节)
替换串$<y>/$<m>
结果对象m.namedGroups.y
反向引用:共用的是「那串文本」,不是 pattern
最常见的误解:以为 \1 是「把第一个组的规则再匹配一次」。不是。它要求第一个组刚刚吃到的那串具体文本
原样再出现。下面同一串输入,左栏用 反向引用 (\w+) \1、右栏用两个独立组 (\w+) (\w+)——
改输入框,看它们什么时候分道扬镳。
试
i:GO go 里 GO 与 go 会算「同一串」吗?
(\w+) \1反向引用 — 第二个词必须 = 第一个词
(\w+) (\w+)两个独立组 — 不要求相等
\w+,右栏「随便两个词」总能配上;左栏却只在两个词字面相同时才命中。
差别就在 \1 锁定的是左组捕获到的文本。这也是它的典型用途——查重复:
叠字 (\w)\1、连写的重复单词 \b(\w+)\s+\1\b。
GO go、左栏 (\w+) \1——不开 flag 时✗(GO ≠ go,逐字不等);
加上 i 就✓。原因是「同一串文本」要按当前的大小写折叠 (case folding) 来判等:开 i 后
GO 与 go 折叠后相同,于是算「同一串」。换言之,反向引用比的不是死板的字节相等,而是在当前 flag 语义下的相等。
(点下面带 /i 的例子直接对比。)
组合:\k<name> —— 用名字做反向引用
把前两件事叠起来:反向引用也能按名字写,即 \k<name>。语义和 \1 完全一样
(要求同一串文本重现),只是把数字换成可读的名字。最经典的两个场景是配对:开闭引号必须同种、
HTML 开闭标签必须同名——「同种 / 同名」正是「共用同一串文本」。
命中高亮
📋 两种「共用」一张表对齐
| 维度 | 命名捕获 (?<x>…) | 反向引用 \1 / \k<x> |
|---|---|---|
| 复用的是什么 | 一个名字 = 某个组的可读别名(指向 pattern 里的组) | 那个组实际匹配到的文本 |
| 作用在哪一层 | 写 / 读层面:pattern、替换串 $<x>、结果 m.namedGroups.x | 匹配运行时:要求同一串文本再次出现 |
| 改变匹配能力吗 | 否 —— 纯别名,和编号组 \1 / groups[1] 等价 | 是 —— 超出正则语言 → 引擎切回溯 |
| 典型用途 | 取字段 namedGroups.year、重排 $<m>/$<d> | 叠字 (\w)\1、重复词、配对引号 / 标签 |
🔗 再深一点
-
MDN · 命名捕获组 · developer.mozilla.org
(?<name>…)的语法、match.groups结果对象、替换串里的$<name>,以及 ES2025 同名组分处互斥分支的规则。 - Regular-Expressions.info · Backreferences · regular-expressions.info 把「反向引用匹配的是文本而非表达式」讲得最透的一篇,配对标签 / 重复词等经典用例齐全,并解释为何它要回溯。