人人都会AI编程

10.5 正则表达式:语法、匹配规则、常用方法、性能问题

更新时间:2026-07-11

正则表达式(Regular Expression)是处理字符串的利器,无论是表单验证、文本提取还是搜索替换,它都能用极简的语法完成复杂的模式匹配。本节不罗列所有元字符,而是聚焦于实际开发中最常用的语法、方法和避坑要点。

10.5.1 创建正则的两种方式

// 字面量方式(推荐用于固定模式)
const regex1 = /abc/i;

// 构造函数方式(适用于动态拼接模式)
const regex2 = new RegExp('abc', 'i');

字面量方式在脚本加载时就完成编译,性能更优;构造函数方式允许从变量拼接正则,但要注意反斜杠的双重转义问题。

10.5.2 核心语法速览

字符类

  • . 匹配除换行符外的任意字符
  • \d 数字,\w 字母数字下划线,\s 空白符
  • [abc] 匹配 a、b 或 c;[^abc] 取反
  • 大写取反:\D 非数字,\W 非单词字符

量词

  • * 0次或多次,+ 1次或多次,? 0次或1次
  • {n} 恰好 n 次,{n,} 至少 n 次,{n,m} n 到 m 次
  • 默认贪婪匹配:尽可能多匹配。在量词后加 ? 开启惰性匹配,如 +?*?

锚点与边界

  • ^ 字符串开始,$ 字符串结束
  • \b 单词边界
  • 使用 m 标志时,^$ 匹配行首和行尾

分组与引用

  • (x) 捕获组,会在结果中保存匹配内容,同时可以用 \1$1 引用
  • (?:x) 非捕获组,只分组不保存
  • x(?=y) 正向先行断言,x(?!y) 负向先行断言
  • (?<=y)x 正向后行断言(ES2018),(?<!y)x 负向后行断言

转义
需要匹配特殊字符(如 .*?+(, ) 等)时,必须用反斜杠转义,否则会被解释为元字符。

标志

  • i 忽略大小写
  • g 全局搜索(不止匹配第一个)
  • m 多行模式(影响 ^$
  • s 允许 . 匹配换行符(ES2018)
  • u 启用 Unicode 模式(正确处理四字节字符)
  • y 粘性搜索(从 lastIndex 位置开始匹配)

10.5.3 常用方法

字符串方法

  • str.match(regex)

g 标志:返回第一个匹配的完整信息(包括捕获组);无匹配返回 null
g 标志:返回所有匹配的完整字符串数组,但不包含捕获组细节。

  • str.matchAll(regex)

必须使用带 g 标志的正则,返回迭代器,每个元素为包含捕获组的详细信息,适合需要批处理所有匹配的场景。

  • str.search(regex)

返回第一个匹配的索引,无匹配返回 -1

  • str.replace(regex, replacement)

最强大的替换方法,支持字符串或回调函数,配合分组可实现复杂替换。

  • str.split(regex)

按正则匹配切割字符串。

正则实例方法

  • regex.test(str)

返回布尔值,判断是否存在匹配。注意:使用 g 标志的正则对象会记住 lastIndex,多次 test 调用可能产生非预期结果,需要手动重置或避免使用 g 标志。

  • regex.exec(str)

返回匹配的详细信息数组(包含 index、input、groups 等),每次执行返回下一个匹配,配合 g 标志可以循环获取所有匹配。

常用操作示例

// 验证邮箱格式(简版,实际生产需更严谨)
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
emailRegex.test('user@example.com');  // true

// 提取数字
'价格:¥99.5元'.match(/[\d.]+/)[0];  // '99.5'

// 批量替换
const str = '2025-04-08';
str.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1');  // '04/08/2025'

// 全局匹配并遍历
const text = 'cat and dog but not catfish';
for (const match of text.matchAll(/\bcat\b/g)) {
  console.log(match.index);  // 0, 13
}

10.5.4 常见性能问题与优化

正则虽然强大,但写得不好会成为性能杀手,尤其在处理大文本或高频调用时。

1. 回溯失控——最隐秘的性能炸弹
正则引擎采用回溯算法尝试所有可能的分支。如果模式中存在多重量词嵌套(如 /(a+)+b/),引擎可能因尝试指数级路径而导致运行时间暴涨。永远避免量词嵌套,单个量词后的量词是警告信号。编写复杂正则时,可以在本地用长字符串进行压力测试。

2. 不必要的捕获组
捕获组 ( ) 会额外保存匹配结果,增加内存消耗。如果不需要引用分组内容,请使用非捕获组 (?: )

3. 全局标志与 lastIndex 陷阱
g 标志的正则对象是有状态的。每次调用 test()exec() 都会更新 lastIndex 属性,下一次调用从该位置继续匹配。在多个地方共享同一个带有 g 标志的正则对象可能导致难以调试的错误。如果只需要判断存在性,不要用 g,或者每次使用前手动设置 regex.lastIndex = 0

4. 选择合理的 API

  • 仅需布尔结果:优先用 test(),不要用 match() 再取长度。
  • 需要第一个匹配内容:用 match()exec()
  • 需要所有匹配的完整信息:用 matchAll()(现代浏览器支持)。
  • 简单字符串查找/包含:应该用 indexOf()includes(),不要用正则,性能差距显著。

5. 预编译正则对象
不要在循环或回调中反复创建完全相同的正则表达式。将正则提取到外部常量,利用引擎的内部缓存,可以显著减少编译开销。

6. 使用具体匹配而非宽泛匹配
/.+@.+/ 匹配邮箱不仅不准确,还容易造成大量回溯。更具体的字符类(如 [^\s@]+)能更快排除不可能的分支,提升匹配速度。

正则表达式是一把双刃剑:用得好,代码简洁高效;用得不好,它可能把浏览器渲染主线程拖垮。理解它的回溯本质和执行机制,是写出健壮、高效正则的前提。