B08 正则表达式
B08 正则表达式
摘要:正则(regular expression,描述字符串模式的表达式)用于三类操作:判定是否匹配、提取片段、替换内容。字面量形式在加载时创建,构造形式用于运行时拼接。贪婪量词与回溯是性能问题的两个来源,用户输入拼进正则前必须转义。本文给出语法速查、四个常用方法的返回值差异、三类业务模式与两项性能守则。
关键词:正则,量词,捕获组,断言,回溯
所属:frontend/js / B-进阶 | 上一篇:B07-Map与Set集合类型 | 下一篇:B09-事件对象与自定义事件 | 返回:B00-总结
1 引言
表单里随处可见的 replace(/\s/g, '') 与 /^1\d{10}$/ 是最容易被复制粘贴却最难自己写对的一类代码。写错的表现不是报错,而是匹配到不该匹配的内容。
本文给出可自行推导的写法,而不是模式集合。范围见表 1-1。
| 项 | 本文覆盖 | 本文不覆盖 |
|---|---|---|
| 语法 | 字符类、量词、分组、断言、标志位 | 引擎实现与自动机理论 |
| 方法 | test、exec、match、replace |
复杂文本解析(解析器) |
| 安全 | 输入转义、回溯控制 | 正则注入的攻防细节 |
表 1-1 本文覆盖范围
2 预备知识
| 项 | 要求 | 核对方式 |
|---|---|---|
| 环境 | Node.js 20+ 或 Chrome 130+ | 控制台能执行正则 |
| 前置 | A07 | 字符串方法已掌握 |
| 文件 | js-practice/regex.mjs |
编码 UTF-8 |
表 2-1 环境与前置要求
3 原理与实现
3.1 从模式到匹配结果
正则按从左到右尝试匹配,量词决定重复次数,失败时回溯重试,如图 3-1 所示。
flowchart TD
A["正则 + 标志位"] --> B["从左向右尝试匹配"]
B --> C{"量词是贪婪还是惰性"}
C -->|贪婪 默认| D["先取最多,失败再回退"]
C -->|惰性 加 ?| E["先取最少,失败再多吃"]
D --> F{"是否命中分组"}
E --> F
F -->|是| G["捕获组写入结果数组"]
F -->|否| H["只返回整体匹配"]
G --> I["返回 match / exec 结果"]
H --> I
图 3-1 正则的匹配流程与量词取回退方向
图上可见:量词只改变「先多试还是先少试」,最终都靠回溯收敛;嵌套量词过多时回溯次数会爆炸。
3.2 完整实现
// js-practice/regex.mjs;运行环境:Node.js 20+
// 1 两种创建方式的差异
const literal = /\d+/g; // 加载时创建,g 标志共享 lastIndex
const dynamic = new RegExp('\\d+', 'g');
console.log(literal.source, dynamic.source); // \d+ \d+
// 2 四个方法的返回值差异
const text = '订单 A102 与 A103';
console.log(/A\d+/.test(text)); // true
console.log(/A\d+/.exec(text)[0]); // A102
console.log(text.match(/A\d+/)); // ['A102']
console.log(text.match(/A(\d+)/)); // ['A102', '102']
console.log(text.replace(/A(\d+)/g, '#$1')); // 订单 #102 与 #103
// 3 贪婪与惰性
console.log('"<b>x</b>"'.match(/<.*>/)[0]); // <b>x</b>,贪婪
console.log('"<b>x</b>"'.match(/<.*?>/)[0]); // <b>,惰性
// 4 分组、命名组与非捕获
const date = '2026-10-31';
const m = /^(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})$/.exec(date);
console.log(m.groups); // { y:'2026', m:'10', d:'31' }
console.log('a1 b2'.match(/(?:[a-z])(\d)/g)); // ['a1', 'b2']
// 5 断言:只判定位置,不消耗字符
console.log('订单102件'.replace(/(?<=订单)\d+/, '88')); // 订单88件
console.log('1a2b'.match(/\d(?=[a-z])/g)); // ['1', '2']
// 6 业务模式
const rules = {
phone: /^1[3-9]\d{9}$/,
email: /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/,
slug: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
blank: /^\s*$/,
};
console.log(rules.phone.test('13800138000')); // true
console.log(rules.email.test('a@b.co')); // true
console.log(rules.slug.test('css-modules')); // true
// 7 用户输入必须先转义再拼进正则
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const keyword = 'v1.0(beta)';
const safe = new RegExp(escapeRegExp(keyword), 'g');
console.log('v1.0(beta) 发布'.replace(safe, 'X')); // X 发布
常用语法见表 3-1。
| 类别 | 写法 | 含义 |
|---|---|---|
| 字符类 | \d \w \s [a-z] [^0-9] |
数字、单词字符、空白、范围、取反 |
| 量词 | * + ? {2,4} +? |
任意、至少一个、可选、区间、惰性 |
| 分组 | (…) (?:…) (?<name>…) \1 |
捕获、非捕获、命名组、反向引用 |
| 边界 | ^ $ \b |
行首、行尾、单词边界 |
| 断言 | (?=…) (?!…) (?<=…) (?<!…) |
前向肯定/否定、后向肯定/否定 |
表 3-1 正则语法速查
标志位见表 3-2。
| 标志 | 含义 | 注意 |
|---|---|---|
g |
全局匹配 | 带 g 时 test/exec 会推进 lastIndex |
i |
忽略大小写 | 只影响 ASCII 之外的部分字符 |
m |
多行模式,^/$ 匹配每行 |
与 s 不冲突 |
s |
让 . 匹配换行 |
默认 . 不含 \n |
u |
按 Unicode 码位处理 | 处理表情与代理对时必加 |
y |
粘性匹配,从 lastIndex 起 |
与 g 组合用于词法分析 |
表 3-2 六个标志位的作用
4 验证与讨论
- 执行脚本,确认第 2 组输出
true、A102、['A102']、['A102','102']、订单 #102 与 #103。 - 把
/<.*>/加上?,确认匹配结果从<b>x</b>变为<b>。 - 连续两次执行
/A\d+/g.exec(text),确认第二次返回A103,说明lastIndex被推进。 - 去掉
escapeRegExp直接用new RegExp(keyword),确认因括号被当作分组而报错或匹配失败。
| 检查项 | 预期结果 | 说明 |
|---|---|---|
| 捕获组 | 结果数组含整体与分组 | 表 3-1 |
| 命名组 | groups 可读到字段名 |
3.2 节 |
| 断言 | 只判定位置不消耗字符 | 表 3-1 |
| 转义 | 含元字符的关键词正确匹配 | escapeRegExp |
| lastIndex | 带 g 时下次从上次继续 |
表 3-2 |
表 4-1 五项验收
讨论与局限。 嵌套量词(如 /(a+)+$/)在长输入上会产生指数级回溯,属于 ReDoS 风险来源,用户输入必须限制长度并避免嵌套量词。另,正则不适合解析嵌套结构(HTML、JSON),此类需求应使用解析器。
5 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 全局正则第二次匹配失败 | lastIndex 未重置 |
每次新建正则,或手动置 lastIndex = 0 |
| 匹配到不该匹配的内容 | 缺少 ^ $ 边界 |
校验类正则一律加首尾锚点 |
| 括号被当成分组 | 用户输入未转义 | 用 escapeRegExp 处理后再构造 |
| 替换只发生一次 | 未加 g 标志 |
全量替换加 g |
| 表情符号匹配错乱 | 未加 u 标志 |
加 u 后按码位处理 |
| 长输入下卡死 | 嵌套量词导致回溯爆炸 | 改写为非嵌套模式并限制输入长度 |
表 5-1 正则的六类问题
6 结论
| 序号 | 结论 | 依据 |
|---|---|---|
| 1 | 量词只改变尝试方向,收敛靠回溯 | 图 3-1 |
| 2 | 校验类正则必须加 ^ 与 $ |
表 5-1 |
| 3 | 用户输入拼进正则前必须转义 | 3.2 节 |
| 4 | 嵌套结构不用正则解析 | 4 节讨论 |
表 6-1 结论汇总
- 上一篇:B07-Map与Set集合类型
- 下一篇:B09-事件对象与自定义事件
- 返回:B00-总结 | 科目 README
参考文献
[1] MDN Web Docs · 正则表达式,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions(访问于 2026-10-31)
[2] ECMA-262 · 22.2 RegExp Objects,https://tc39.es/ecma262/#sec-regexp-objects(访问于 2026-10-31)

评论 0
还没有评论,快来抢沙发吧~