本文深入解析JavaScript正则表达式中的分组机制,详细区分捕获性与非捕获性分组的用法及差异。重点剖析(\d+)与(\d)+在贪婪模式下的匹配行为不同,揭示后者仅捕获最后一次匹配结果的常见陷阱,并提供非捕获分组(?:pattern)的解决方案。
此前曾撰写过一篇关于正则表达式入门的文章,自认为对正则表达式已有一定了解,但今日又遇到了一个典型问题,或许源于自身不够细致。今天将重点与大家分享JavaScript正则表达式中关于分组的知识。若你对JS正则表达式尚不熟悉,可点击此处了解更多基础知识。
正则表达式分组基础
分组在正则表达式中的应用十分广泛,我的理解是,分组即由一对括号()表示,每一对括号定义了一个独立的分组。
根据行为特性,分组主要可以分为以下两类:
- 捕获性分组
- 非捕获性分组
- 具体来看捕获性分组
捕获性分组与贪婪模式陷阱
在调用match或exec等函数时,捕获性分组的结果会以数组的第二项、第三项等形式返回。我们先通过一个示例来观察这一现象。
var reg = /test(\d+)/;
var str = 'new test001 test002';
console.log(str.match(reg));
//["test001", "001", index: 4, input: "new test001 test002"]
代码中的(\d+)构成一个分组(部分语境下也称为子模式),两者含义一致。在上述示例中,test001为完全匹配的结果。然而,分组匹配是从整个完全匹配结果(即test001)中提取与子模式\d+相匹配的字符部分,此处显然为001。
但今日遇到的情况有所不同,具体表现如下:
var reg = /test(\d)+/;
var str = 'new test001 test002';
console.log(str.match(reg));
//["test001", "1", index: 4, input: "new test001 test002"]
主要区别在于将(\d+)修改为(\d)+。尽管整个匹配结果仍为test001,但第一个分组匹配到的结果却发生了变化。
我们需要逐步分析这两者的区别。
对于(\d+),整个表达式视为一个分组。默认情况下,匹配模式为贪婪模式,即尽可能多地匹配字符。因此,\d+匹配到的结果是001,外加一对括号形成分组,故第一个分组捕获的结果为001。
再看第二个示例中的(\d)+,同样采用贪婪模式。首先匹配字符0,随后匹配第二个0,最后匹配1,匹配过程至此结束。
表面上看,这与第一个例子的匹配过程似乎没有区别,但这里的分组(\d)仅表示匹配单个数字。
按照之前的理解,可能会认为会捕获最先匹配到的结果,即0,但这种理解是错误的。由于整个匹配过程遵循贪婪模式,旨在尽可能多地匹配字符。
因此,分组中的(\d)会捕获最后一次匹配到的结果,即1。
若采用非贪婪模式,则会尽可能少地进行匹配。
var reg = /test(\d)+?/;
var str = 'new test001 test002';
console.log(str.match(reg));
//["test001", "0", index: 4, input: "new test001 test002"]
在此情况下,(\d)的匹配结果即为0。尽管后续仍有可匹配的字符,但非贪婪模式要求尽可能少地匹配,因此结果为0。
非捕获性分组的应用
接下来介绍非捕获性分组。
var reg = /test(?:\d)+/;
var str = 'new test001 test002';
console.log(str.match(reg));
//["test001", index: 4, input: "new test001 test002"]
非捕获性分组适用于需要用到括号,但不希望其成为捕获性分组,即不希望该分组被match、exec等函数获取到的场景。
通常,在括号内部前添加?:即可实现,即写成(?:pattern),这样便构成了一个非捕获性分组。
此时,match的结果中将不再包含分组匹配到的内容,例如上述示例中便少了第二项的1。
本文重点阐述(\d+)与(\d)+的区别,这也是今日遇到的实际问题。若有错误之处,欢迎指正。