B08 正则表达式

2026-10-31 17:120 阅读约 12 分钟JavaScript

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 验证与讨论

  1. 执行脚本,确认第 2 组输出 true、A102、['A102']、['A102','102']、订单 #102 与 #103。
  2. 把 /<.*>/ 加上 ?,确认匹配结果从 <b>x</b> 变为 <b>。
  3. 连续两次执行 /A\d+/g.exec(text),确认第二次返回 A103,说明 lastIndex 被推进。
  4. 去掉 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 结论汇总

参考文献

[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

0/2000

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