Skip to content

正则表达式

字符匹配 · 位置匹配 · 括号 · 回溯 · API


一、字符匹配基础

正则 = 匹配字符 或 匹配位置

1. 两种模糊匹配

  • 横向模糊(量词):匹配长度不固定,如 {m,n} 表示出现 m~n 次
  • 纵向模糊(字符组):某一位可以是多种字符之一,如 [abc]

2. 字符组简写

简写等价形式含义
\d[0-9]数字
\D[^0-9]非数字
\w[0-9a-zA-Z_]单词字符
\W[^0-9a-zA-Z_]非单词字符
\s[ \t\v\n\r\f]空白符
\S[^ \t\v\n\r\f]非空白符
.[^\n\r\u2028\u2029]通配符(几乎任意字符)
  • 匹配任意字符:[\d\D][\w\W][\s\S][^]

3. 量词

量词含义
{m,n}出现 m~n 次
{m,}至少 m 次
{m}恰好 m 次
?{0,1} 出现 0 或 1 次
+{1,} 至少 1 次
*{0,} 任意次

4. 贪婪 vs 惰性匹配

  • 贪婪(默认):尽可能多匹配,如 \d{2,5} 遇到 "123456" 会匹配 "12345"
  • 惰性(量词后加 ?):尽可能少匹配,如 \d{2,5}? 遇到 "123456" 会匹配 "12"
  • 惰性量词:{m,n}? {m,}? ?? +? *?

5. 分支结构(也是惰性的)

  • (p1|p2|p3) 中前面匹配成功后面不再尝试
  • /good|goodbye/ 匹配 "goodbye" 只会得到 "good"
  • 应写为 /goodbye|good/(长的放前面)

二、位置匹配(6 个锚字符)

锚字符含义
^匹配开头(多行模式下匹配行开头)
$匹配结尾(多行模式下匹配行结尾)
\b单词边界(\w\W 之间)
\B非单词边界
(?=p)正向先行断言(p 前面的位置)
(?!p)负向先行断言(非 p 前面的位置)
  • ES6 还支持 (?<=p)(正向后行断言)和 (?<!p)(负向后行断言)
  • 位置等价于空字符 """hello" 等价于 "" + "h" + "" + "e" + "" + ...

经典应用

js
// 千位分隔符
"12345678".replace(/(?!^)(?=(\d{3})+$)/g, ',')
// => "12,345,678"

// 密码验证(至少包含两种字符,6-12位)
var reg = /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/

三、括号的三大作用

  1. 分组:使量词作用于整体 /(ab)+/
  2. 捕获分组:提取数据 + 替换引用
    js
    // 提取日期各部分
    "2017-06-12".match(/(\d{4})-(\d{2})-(\d{2})/)
    // => ["2017-06-12", "2017", "06", "12"]
    
    // 替换引用分组
    "2017-06-12".replace(/(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1")
    // => "06/12/2017"
  3. 反向引用\1 引用第一个分组匹配的内容
    js
    // 日期分隔符前后一致
    /\d{4}(-|\/|\.)\d{2}\1\d{2}/.test("2017-06-12")  // true
    /\d{4}(-|\/|\.)\d{2}\1\d{2}/.test("2017-06/12")  // false

非捕获分组 (?:p):只用括号功能,不捕获数据


四、回溯法原理

  • 正则引擎从左到右逐字符尝试匹配
  • 遇到贪婪量词时先尽可能多匹配,如果后续匹配失败则逐步回退(回溯)
  • 回溯是性能杀手,应避免不必要的回溯
js
// 低效(有回溯)
/id=".*?"/
// 高效(无回溯)
/id="[^"]*"/

五、正则相关 API 要点

1. match 返回值因 g 修饰符不同

js
"2017.06.27".match(/\b(\d+)\b/)
// => ["2017", "2017", index: 0, input: "2017.06.27"]  ← 有分组信息

"2017.06.27".match(/\b(\d+)\b/g)
// => ["2017", "06", "27"]  ← 只有匹配结果,无分组和 index

2. exec 比 match 更强(支持迭代匹配)

js
var regex = /\b(\d+)\b/g
var result
while (result = regex.exec("2017.06.27")) {
  console.log(result[0], regex.lastIndex)
}
// 2017 4 → 06 7 → 27 10

3. 修饰符 g 对 test/exec 的影响

  • g 时,每次匹配后 lastIndex 会更新,下次从新位置开始
  • 陷阱:同一个带 g 的正则多次 test() 结果可能不一致
js
var regex = /a/g
regex.test("a")     // true, lastIndex=1
regex.test("aba")   // true, lastIndex=3
regex.test("ababc") // false! lastIndex=0(从位置3开始找不到a)

4. replace 的第二个参数

字符串形式$1~$99 引用分组、$& 匹配的子串、$` 左边文本、$' 右边文本

函数形式

js
"1234 2345".replace(/(\d)\d{2}(\d)/g, function(match, $1, $2, index, input) {
  // match=完整匹配, $1/$2=分组, index=位置, input=原字符串
})

5. 修饰符总结

修饰符含义
g全局匹配
i忽略大小写
m多行模式(影响 ^$

相关