正则表达式分组
使用 () 的捕获组和反向引用,将模式的一部分分组以捕获和重用匹配的子字符串。
正则表达式分组 (Group) 是用括号 () 把模式的一部分括起来进行分组的功能。分组主要有三种类型:捕获组 (...)、命名捕获组 (?<name>...) 和非捕获组 (?:...)。它们的用途各不相同,按场景区分使用可以提高正则表达式的可读性和性能。
捕获组会记住匹配到的子字符串,可以通过反向引用或替换操作重用。在 JavaScript 中,可以从 match() 或 exec() 的结果数组中取得捕获组的值。例如用 /(\d{4})-(\d{2})-(\d{2})/ 匹配日期字符串后,可以分别取得年、月、日。
一个陷阱是,即使给分组加上量词,也不会按重复的次数逐次保留结果。把 /(\w)+/ 应用于 "abc" 时,整体会匹配 "abc",但 1 号分组中留下的只有最后一次的 "c"。如果想取出重复的每一次结果,就不要在分组的外侧做重复,而应用 matchAll() 逐个遍历。
使用命名组 (?<name>...) 后可以通过 groups.name 访问,可读性大幅提高。写成 /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/ 这样,就能用 result.groups.year 取得年份,比按编号引用的意图更明确。
非捕获组 (?:...) 只进行分组,不记住匹配结果。它在实用上的好处不在于速度,而在于不占用分组编号。在中途插入 1 个捕获组,其后的编号会全部错位,用 $1 或 \1 的引用会随之失效,而非捕获组不会影响编号。速度差异按 Node.js 26 实测,对 2,000 字符的输入匹配 20,000 次约 10 毫秒,在通常用途中感觉不到差别。例如 (?:http|https):// 会对 URL 的协议部分进行分组,但不做捕获 (这个例子也可以写成 https?://)。
使用反向引用 \1、\2 可以在同一模式内匹配与之前捕获到的字符串相同的字符串。检测重复单词 (\w+)\s+\1 是典型例子。经常被引用的 HTML 标签配对验证 <(\w+)>.*?</\1>,在有嵌套时会被内层的结束标签截断,而且仅大小写不同也会判为不匹配,因此不能用于确认标签的配对。在替换操作中,用 $1、$2 引用捕获组的内容,可以实现文本的重组。
在与字符计数的关联上,分组可以用于从文本中提取特定模式,并统计其出现次数或字符数。例如用分组捕获 URL 或电子邮件地址,就能做出统计正文中包含多少个这类元素的分析。