正则表达式(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@]+)能更快排除不可能的分支,提升匹配速度。
正则表达式是一把双刃剑:用得好,代码简洁高效;用得不好,它可能把浏览器渲染主线程拖垮。理解它的回溯本质和执行机制,是写出健壮、高效正则的前提。