模式中的反向引用:\N
和 \k<name>
前言
我们不仅可以在结果或替换字符串中使用捕获组 (...)
的内容,还可以在模式本身中使用它们。
按捕获组的编号,反向引用\n
可以使用 \N
在模式中引用一个组,其中 N
是组号。
为了弄清那为什么有帮助,让我们考虑一项任务。
我们需要找到带引号的字符串:单引号 '...'
或双引号 "..."
– 应匹配两种变体。
如何找到它们?
我们可以将两种引号放在方括号中:['"](.*?)['"]
,但它会找到带有混合引号的字符串,例如 "...'
和 '..."
。当一种引号出现在另一种引号内,比如在字符串 "She's the one!"
中时,便会导致不正确的匹配:
let str = `He said: "She's the one!".`;
let regexp = /['"](.*?)['"]/g;
// 不是我们想要的结果
console.log(str.match(regexp)); // [ `"She'` ]
如我们所见,该模式找到了一个开头的引号 "
,然后文本被匹配,直到另一个引号 '
,该匹配结束。
为了确保模式查找的结束引号与开始的引号完全相同,我们可以将其包装到捕获组中并对其进行反向引用:(['"])(.*?)\1
。
这是正确的代码:
let str = `He said: "She's the one!".`;
let regexp = /(['"])(.*?)\1/g;
console.log(str.match(regexp)); // [ `"She's the one!"` ]
现在可以了!正则表达式引擎会找到第一个引号 (['"])
并记住其内容。那是第一个捕获组。
\1
在模式中进一步的含义是“查找与第一(捕获)分组相同的文本”,在我们的示例中为完全相同的引号。
与此类似,\2
表示第二(捕获)分组的内容,\3
– 第三分组,依此类推。
请注意:
如果我们在组中使用
?:
,那么我们将无法引用它。用(?:...)
捕获的组被排除,引擎不会存储。
不要搞混了: 在模式中用
\1
,在替换项中用:$1
在替换字符串中我们使用美元符号:
$1
,而在模式中 – 使用反斜杠\1
。
按捕获组的命名,反向引用\k<name>
如果正则表达式中有很多括号对(注:捕获组),给它们起个名字方便引用。
要引用命名组,我们可以使用:\k<name>
。
在下面的示例中引号组命名为 ?<quote>
,因此反向引用为 \k<quote>
:
let str = `He said: "She's the one!".`;
let regexp = /(?<quote>['"])(.*?)\k<quote>/g;
console.log(str.match(regexp)); // [ `"She's the one!"` ]
总结
通过捕获组编号复用正则
\1
、\2
等等:正则表达式的捕获组都是有编号的,引擎会记住本次匹配中每个捕获组匹配的结果,我们可以在正则里复用捕获组的模式,使用
\n
的方式,比如:/(a)(b)\1\2/g
,\1
匹配的结果就会与第一组捕获组中匹配的结果一致,\2
就会与第二组捕获组匹配的结果一致,这样就相当于复用了规则,不用重写一遍。jslet str = 'abab' console.log(str.match(/(a)(b)\1\2/g)); // [ 'abab' ]
通过捕获组别名复用正则
\k<quote>
:如果捕获组嵌套的层级较深时,不好看出来是第几个捕获组,所以可以通过别名的方式复用,比如:
/(?<a>a)(?<b>b)\k<a>\k<b>/g
jslet str = 'abab' console.log(str.match(/(?<a>a)(?<b>b)\k<a>\k<b>/g)); // [ 'abab' ]
被排除的捕获组不能复用,因为已经其匹配的结果引擎不再会存储了。
(?:xxx)
jslet str = 'abab' let str2 = 'abb2' console.log(str.match(/(?:a)(b)\1\2/g)); // null console.log(str2.match(/(?:a)(b)\1/g)); // [ 'abb' ] 只有一个捕获组了