本文深入解析JavaScript正则表达式中的分组机制,涵盖捕获型、非捕获型及前瞻型分组。详细讲解如何通过括号捕获字符串、利用$1等引用捕获内容,以及在replace方法中的应用。同时介绍反向引用、嵌套分组捕获顺序及前瞻断言的区别,帮助开发者精准控制正则匹配行为。
分组与候选
分组
若需匹配连续重复的特定字符串,例如三个连续的“kid”,可使用如下正则表达式:
/kidkidkid/
相比之下,采用量词的方式书写更为简洁优雅:
/(kid){3}/
上述代码中,由圆括号包围的部分被称为“分组”。
候选
在一个分组内部,可以定义多个备选表达式,各选项之间使用竖线符号“|”进行分隔:
var reg = /I love (him|her|it)/;
reg.test('I love him') // true
reg.test('I love her') // true
reg.test('I love it') // true
reg.test('I love them') // false
此处的“|”符号逻辑上等同于“或”的关系。
捕获与引用
捕获与引用
当正则表达式成功匹配到字符串时,匹配到的内容会被暂时保存。其中,由分组捕获的字符串会从编号1开始依次存储,随后便可以在后续操作中引用这些已捕获的字符串:
var reg = /(\d{4})-(\d{2})-(\d{2})/
var date = '2010-04-12'
reg.test(date)
RegExp.$1 // 2010
RegExp.$2 // 04
RegExp.$3 // 12
符号$1用于引用第一个被捕获的字符串,$2引用第二个,依此类推。
与replace配合
在String.prototype.replace方法的参数中,可以直接引用之前捕获的字符串。例如,若要将日期格式从“12.21/2012”转换为“2012-12-21”,实现方式如下:
var reg = /(\d{2}).(\d{2})\/(\d{4})/
var date = '12.21/2012'
date = date.replace(reg, '$3-$1-$2') // date = 2012-12-21
此外,向replace方法传递迭代函数,有时能够更优雅地解决复杂替换问题。
将违禁词替换为等长度的星号是一项常见功能。假设文本内容为“kid is a doubi”,其中“kid”和“doubi”为违禁词,转换后的结果应为“*** is a *****”。具体实现代码如下:
var reg = /(kid|doubi)/g
var str = 'kid is a doubi'
str = str.replace(reg, function(word){
return word.replace(/./g, '*')
})
嵌套分组与反向引用
嵌套分组的捕获
当遇到类似嵌套结构的正则表达式时,捕获的顺序如何确定?通过实际测试可以验证:
var reg = /((kid) is (a (doubi)))/
var str = "kid is a doubi"
reg.test( str ) // true
RegExp.$1 // kid is a doubi
RegExp.$2 // kid
RegExp.$3 // a doubi
RegExp.$4 // doubi
其规则是按照左括号出现的先后顺序进行捕获。
反向引用
正则表达式内部同样支持引用操作,这被称为“反向引用”:
var reg = /(\w{3}) is \1/
reg.test('kid is kid') // true
reg.test('dik is dik') // true
reg.test('kid is dik') // false
reg.test('dik is kid') // false
符号\1用于引用第一个分组所捕获的字符串,这意味着表达式的匹配结果是由动态内容决定的。
需要注意的是,如果引用的编号超出了实际存在的分组范围,该引用将被视为普通字符表达式:
var reg = /(\w{3}) is \6/;
reg.test( 'kid is kid' ); // false
reg.test( 'kid is \6' ); // true
分组类型详解
分组的类型
分组主要包含四种类型:
捕获型 - () 非捕获型 - (?:) 正向前瞻型 - (?=) 反向前瞻型 - (?!) 前文所述均为捕获型分组,唯有此类分组会暂存匹配到的字符串。
非捕获型分组
有时仅需对表达式进行分组而不需要捕获结果,此时可使用非捕获型分组,其语法为左括号后紧跟“?:”:
var reg = /(?:\d{4})-(\d{2})-(\d{2})/
var date = '2012-12-21'
reg.test(date)
RegExp.$1 // 12
RegExp.$2 // 21
在此示例中,(?:\d{4})分组不会捕获任何字符串,因此$1对应的是由(\d{2})捕获的内容。
正向与反向前瞻型分组
这类似于站在原地向前眺望:
正向前瞻型分组 - 检查前方是否匹配特定内容? 负向前瞻型分组 - 检查前方是否不匹配特定内容? 这种表述较为晦涩,更直观的理解是肯定表达式与否定表达式。以下是一个正向前瞻的示例:
var reg = /kid is a (?=doubi)/
reg.test('kid is a doubi') // true
reg.test('kid is a shabi') // false
该表达式检查“kid is a”之后是否紧跟“doubi”,仅当匹配时才成功。
而负向前瞻的逻辑则恰好相反:
var reg = /kid is a (?!doubi)/
reg.test('kid is a doubi') // false
reg.test('kid is a shabi') // true
既然前瞻型分组也不捕获值,它与非捕获型的区别何在?通过对比示例可见:
var reg, str = "kid is a doubi"
reg = /(kid is a (?:doubi))/
reg.test(str)
RegExp.$1 // kid is a doubi
reg = /(kid is a (?=doubi))/
reg.test(str)
RegExp.$1 // kis is a
由此可见,非捕获型分组匹配到的字符串仍会被外层的捕获型分组所捕获,但前瞻型分组则不会。当需要参考后续内容但不希望将其一并捕获时,前瞻型分组便发挥了作用。
最后需指出,JavaScript不支持后瞻型分组。