正则表达式实战
正则擅长描述"形状可枚举、结构稳定"的文本模式。它的强大常常被滥用:把本该用解析器处理的格式(HTML、嵌套结构)也塞进正则,最后写出一个没人能维护的怪物。这一篇讲实战能用到的部分,以及边界在哪。
常用模式:先记住几个能覆盖大多数业务的
js
// 邮箱:够用就行,不必追求 RFC 完整
const email = /^[\w.+-]+@[\w-]+(\.[\w-]+)+$/
// 中国大陆手机号(11 位,1 开头)
const phone = /^1[3-9]\d{9}$/
// URL(简化版,只校验协议和主机)
const url = /^https?:\/\/[\w.-]+(:\d+)?(\/[^\s]*)?$/
// 18 位身份证号(含末位校验位的简化版本)
const idCard = /^\d{17}[\dXx]$/业务校验不要追求"完美正则"。邮箱 RFC 5322 的完整正则长达几千字符,没有意义。校验到"看起来对"就够了,真正有效与否要让后端发邮件/发短信验证。
捕获组与反向引用
用 () 分组既能提取子串,也能配合反向引用匹配重复内容:
js
// 提取日期各部分
const m = '2026-08-09'.match(/(\d{4})-(\d{2})-(\d{2})/)
console.log(m[1], m[2], m[3]) // 2026 08 09
// 反向引用:匹配成对单词
'hello hello world'.match(/(\w+)\s\1/) // 'hello hello'
// 命名捕获组更易读
const re = /(?<year>\d{4})-(?<month>\d{2})/
const { year, month } = re.exec('2026-08').groups| 写法 | 含义 |
|---|---|
(abc) | 捕获组 |
(?:abc) | 非捕获组(不保存匹配,性能更好) |
(?<name>abc) | 命名捕获组 |
\1 | 反向引用第 1 个捕获组 |
不需要提取的分组一律用 (?:...),省内存也更快。
贪婪与非贪婪
默认量词是贪婪的,会尽可能多匹配;加 ? 变成非贪婪(懒惰),尽可能少匹配。这是处理"配对标签/引号内容"的关键。
js
const html = '<a>1</a><a>2</a>'
html.match(/<a>.*<\/a>/) // 贪婪:'<a>1</a><a>2</a>',跨过中间的标签
html.match(/<a>.*?<\/a>/) // 非贪婪:'<a>1</a>',最短匹配处理 HTML/JSON 这种结构化文本,正则只能做粗筛,真正的解析要交给 DOMParser 或
JSON.parse。
replace 的回调:被低估的能力
replace 第二个参数可以是函数,能拿到每个捕获组、位置和原串,做复杂替换很顺手:
js
// 模板插值
'你好 {name},余额 {amount}'.replace(/\{(\w+)\}/g, (_, key) => data[key])
// 千分位
'1234567'.replace(/\B(?=(\d{3})+(?!\d))/g, ',') // '1,234,567'
// 敏感词脱敏
'18612345678'.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') // '186****5678'test / exec / match 怎么选
| 方法 | 用途 | 注意 |
|---|---|---|
re.test(str) | 只判断是否匹配 | 返回 boolean,最快 |
str.match(re) | 提取匹配内容 | 带 g 标志返回所有匹配数组;不带返回第一组+捕获组 |
re.exec(str) | 反复匹配、提取带位置信息 | 配合 g 可循环调用 |
str.replace(re, ...) | 替换 | 回调形式非常强大 |
带 g 标志的 exec 会记住 lastIndex,循环调用可以遍历所有匹配;但同一个正则对象在多次 test 调用时也会受 lastIndex 影响,是个经典坑:
js
const re = /a/g
re.test('abc') // true,lastIndex 变为 1
re.test('abc') // false!lastIndex 已经超过需要无状态判断时,要么不加 g,要么每次用新字面量。
性能与 ReDoS
正则在最坏情况下的回溯可能指数级增长。攻击者构造的特殊字符串能让一个看似正常的正则把 CPU 跑满,这就是 ReDoS(正则拒绝服务)。
危险模式:嵌套量词、量词叠加、交替分支里的重叠。例如 (a+)+、(a|a)*、(\w+)+ 都是高危写法。
js
// 危险:用户输入能让它卡死
/(a+)+$/.test('a'.repeat(30) + '!') // 指数级回溯
// 更安全:避免重叠量词
/a+$/.test('a'.repeat(30) + '!')防范:对用户输入别用复杂正则;服务端校验要设超时;优先用更确定的字符类(如 [a-z] 而非 \w)配合有限重复 {1,20}。
何时别用正则
- 解析 HTML / XML / JSON:用专用解析器。
- 嵌套层数不定的结构(括号配对、标签嵌套):正则不是上下文无关文法解析器。
- 简单的"是否包含某子串":
includes/startsWith更快更可读。 - 需要在多个分支里做语义判断的:写成普通函数更容易维护。
一个判断标准:如果一周后你自己都看不懂这行正则,就该拆成函数。
检查表
- 校验类正则够用即可,不追求覆盖所有边缘 RFC 情况。
- 不需要提取的分组用
(?:...),命名捕获组提升可读性。 - 注意贪婪/非贪婪的区别,处理成对内容用非贪婪。
- 警惕嵌套量词带来的 ReDoS 风险,用户输入场景务必设上限。
- HTML、嵌套结构、JSON 解析用专用工具,别硬塞给正则。