很多文章讲述正则是从元字符的认知到量词和基本匹配,再到零宽断言贪婪匹配这些高阶知识讲解的。本文另辟蹊径,只讲述问号的应用,从一个点剖析正则表达式的一些非全面覆盖的知识点。

量词 quantifier

问号表示前面的字符为可选,也就是出现的次数是 0 或者 1 次。

let str = `The car is parked in the garage.`;
let reg = /T?he/g;

str.match(reg) // ["The", "he"]

命名一个捕获组

使用 ?<> 来命名一个捕获组,然后使用 \k<> 来在表达式中引用组:

let str = "aaa123aaaaaa"
let reg = /(?<word>\w+)(\d+)\k<word>/g

str.match(reg) // ["aaa123aaa"]

需要注意捕获到的内容是 exactly the same 的,如果 str="aaa123bbb" 就跟表达式不匹配。所以我们可以用来处理引号的匹配来获取各语言里面的话语内容:

let str = `He said: "She's the one!".`;
let reg = /(?<quote>['"])(.*?)\k<quote>/g;

str.match(reg) // [""She's the one!""]

开启 lazy 模式

正则表达式默认采用贪婪匹配模式,在该模式下意味着会匹配尽可能长的子串。我们可以使用?将贪婪匹配模式转化为惰性匹配模式。

let str = `The fat cat sat on the mat.`;
let reg = /(.*at)/g;

str.match(reg) // ["The fat cat sat on the mat"]

reg = /(.*?at)/g;
str.match(reg) // ["The fat", " cat", " sat", " on the mat"]

零宽断言

也叫前后预查(匹配),用来判断要匹配的内容是否在另一个约束条件周围。匹配内容在前约束条件在后的叫先行断言(look ahead),反之则为后发断言(look behind)。两种约束又分为正(positive)和负(negative),用来表达包含和排除。

符号描述
?=正先行断言-存在
?!负先行断言-排除
?<=正后发断言-存在
?<!负后发断言-排除

分别举例:


let str="1 人民币约等于 $7($是美元符号)"
let reg = /\$/g
str.match(reg) // (2) ["$", "$"]

// 正先行
reg = /\$(?=\d+)/g
str.match(reg) // ["$"]

// 负先行
reg = /\$(?!\d+)/g
str.match(reg) // ["$"]

reg = /\d/g
str.match(reg) // (2) ["1", "7"]

// 正后发
reg = /(?<=\$)\d+/g
str.match(reg) // ["7"]

// 负后发
reg = /(?<!\$)\d+/g
str.match(reg) // ["1"]

需要注意的是别看这也是被括号包围的形式,但约束条件却是非捕获的,需要捕获得再包围一个括号。


let str = "1 人民币约等于 $7"
let reg = /(?<=(\$))\d+/;
str.match(reg) // (2) ["7", "$", index: 10, input: "1 人民币约等于 $7", groups: undefined]

可以观察到即使断言组在前,结果 $ 仍出现在 7 之后。先行断言也一样,匹配内容先出现。

忽略捕获组

使用 (?:) 来将后缀的匹配设为非捕获的组,我们也就无法使用 $ 跟数字的形式来引用这个组。

let str = "123aaabbb"
let reg = /(\d+)(.*)/g

reg.test(str) // true
RegExp.$1 // "123"
RegExp.$2 // "aaabbb"

reg = /(\d+)(?:.*)/g
reg.test(str) // true
RegExp.$1 // "123"
RegExp.$2 // ""

结语

以上就是我对正则中问号的总结,希望对您有所帮助!