两种创建方式
JavaScript 中可以用字面量语法或构造函数创建正则表达式:
字面量语法(推荐)
/pattern/flags——在代码解析时编译,适合模式固定的情况。性能更好,且不需要对反斜杠进行双重转义(字面量中 \d 就是 \d,不需要写 \\d)。构造函数语法
new RegExp(pattern, flags)——在运行时构建,适合模式需要动态生成的情况(如用户输入、变量拼接)。注意:构造函数接受字符串,需要对反斜杠双重转义(\\d 才是 \d)。// 字面量语法(推荐,反斜杠不需要双重转义) const re1 = /\d+/g const re2 = /^hello\s+world$/im // 构造函数语法(需要双重转义) const re3 = new RegExp('\\d+', 'g') // 等价于 /\d+/g // 动态构建正则(必须使用构造函数) const keyword = 'hello' const searchRe = new RegExp(keyword, 'gi') // ⚠️ 动态正则必须转义用户输入的特殊字符 function escapeRegex(str) { // 转义所有正则特殊字符 return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } const userKeyword = escapeRegex(userInput) // 先转义 const safeRe = new RegExp(userKeyword, 'gi') // 再构建
String 方法:正则的主战场
JavaScript 中正则最常通过 String 的方法使用:
match() — 返回匹配信息
const str = 'test1 test2 test3' // 无 g 标志:返回第一个匹配及详细信息 str.match(/test(\d)/) // → ['test1', '1', index: 0, input: 'test1 test2 test3', groups: undefined] // result[0] = 整个匹配, result[1] = 第1捕获组 // 有 g 标志:返回所有匹配的字符串数组(但失去捕获组信息!) str.match(/test\d/g) // → ['test1', 'test2', 'test3'] // 无匹配时返回 null(注意不是空数组) str.match(/\d{5}/) // → null // 安全写法:用可选链 const result = str.match(/test(\d)/)?.[1] ?? '默认值'
matchAll() — ES2020,推荐使用
// matchAll 返回迭代器,每个元素都是完整的 Match 对象(含捕获组) // 必须使用 g 标志 const text = '2026-01-01 和 2026-12-31' const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g for (const m of text.matchAll(dateRe)) { console.log(m[0]) // '2026-01-01', '2026-12-31' console.log(m.groups) // { year: '2026', month: '01', day: '01' } console.log(m.index) // 0, 11 } // 转为数组 const matches = [...text.matchAll(dateRe)] const dates = matches.map(m => m.groups)
replace() 和 replaceAll() — 替换操作
// 字符串替换(只替换第一个) 'hello world'.replace(/o/, '0') // 'hell0 world' // 全局替换(加 g) 'hello world'.replace(/o/g, '0') // 'hell0 w0rld' // 数字反向引用($1, $2...) '2026-03-26'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1') // → '03/26/2026' // 命名引用($<name>, ES2018) '2026-03-26'.replace( /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, '$<d>/$<m>/$<y>' ) // → '26/03/2026' // 特殊替换符 'foo bar'.replace(/(\w+)/g, '[$&]') // '[foo] [bar]' $& = 整个匹配 'foo bar'.replace(/(\w+) (\w+)/, "$2 $1") // 'bar foo' 交换 // 函数替换(动态) 'price: 100 dollars'.replace(/\d+/g, n => n * 1.1) // → 'price: 110.00000000000001 dollars'(浮点数问题,实际使用需 toFixed) 'hello world'.replace(/\b\w/g, c => c.toUpperCase()) // → 'Hello World'(每个单词首字母大写) // replaceAll(ES2021)不需要 g 标志的字符串替换 'a.b.c'.replaceAll('.', '-') // 'a-b-c'(安全替换字面字符串)
search() 和 split()
// search:返回首个匹配的起始位置,无匹配返回 -1 'hello world'.search(/world/) // 6 'hello world'.search(/xyz/) // -1 // split:按正则分割 'a1b2c3d'.split(/\d/) // ['a','b','c','d'] ' hello world '.split(/\s+/).filter(Boolean) // → ['hello', 'world'] (filter 去除空字符串) // split 带捕获组:分隔符也出现在结果中 'a,b,,c'.split(/(,)/) // → ['a', ',', 'b', ',', '', ',', 'c']
RegExp 方法
test() — 测试是否匹配
// test 返回布尔值,最简单的验证方法 /^\d{4}$/.test('2026') // true /^\d{4}$/.test('20261') // false /^[a-z]+$/.test('hello') // true
exec() — 循环提取所有匹配
// exec 带 g 标志时,每次调用返回下一个匹配 const re = /\d+/g let m while ((m = re.exec('a1 b22 c333')) !== null) { console.log(`匹配: ${m[0]},位置: ${m.index}`) } // 匹配: 1,位置: 1 // 匹配: 22,位置: 4 // 匹配: 333,位置: 8 // 注意:现代代码建议用 matchAll 代替 exec 循环
WARNING(lastIndex 陷阱)带
g 或 y 标志的正则对象有 lastIndex 状态属性,记录下次匹配的起始位置。多次调用 exec 或 test 时会改变这个状态。如果你在不同字符串上复用同一个带 g 标志的正则对象,lastIndex 可能导致意外行为——第一次匹配可能成功,第二次却失败(因为 lastIndex 没有被重置)。
// ⚠️ lastIndex 陷阱演示 const re = /\d+/g re.test('hello 123') // true,lastIndex 变为 9 re.test('hello 456') // false!因为 lastIndex=9,超过字符串长度,重置为0 re.test('hello 789') // true,重新从0开始 // ✅ 解决方案1:每次创建新正则 function hasDigit(str) { return /\d+/g.test(str) // 字面量每次都是新对象 } // ✅ 解决方案2:不带 g 标志(test 不需要 g) const re2 = /\d+/ // 不加 g re2.test('hello 123') // true(稳定) re2.test('hello 456') // true(稳定) // ✅ 解决方案3:匹配前手动重置 re.lastIndex = 0 re.test('hello 123')
ES2018+ 现代特性
命名捕获组
const { groups } = '2026-03-26'.match( /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/ ) console.log(groups) // { year: '2026', month: '03', day: '26' } // 命名引用在替换中 '26 March 2026'.replace( /(?<day>\d+) (?<month>\w+) (?<year>\d+)/, '$<year>-$<month>-$<day>' ) // → '2026-March-26'
后顾断言(Lookbehind)
// ES2018 才支持后顾断言(Python 一直支持) '$100 €200 £300'.match(/(?<=\$)\d+/g) // ['100']($后的数字) '$100 €200 £300'.match(/(?<!\$)\d+/g) // ['200', '300'](非$后的数字)
dotAll 标志(s)
/foo.bar/s.test('foo\nbar') // true(s 让 . 匹配换行) // 提取多行 HTML 标签内容 '<div>\n content\n</div>'.match(/<div>(.+?)<\/div>/s)?.[1] // → '\n content\n'
Unicode 属性转义(\p{...},需 u 或 v 标志)
// 匹配任意语言的字母 /\p{Letter}+/u.test('Héllo') // true(含重音字母) /\p{Letter}+/u.test('こんにちは') // true(日文) // 匹配汉字 'hello 世界'.match(/\p{Script=Han}+/gu) // → ['世界'] // 匹配表情符号 'hello 😀 world'.match(/\p{Emoji}/gu) // → ['😀'] // 注意:一定要加 u 标志,否则 \p 无效
ES2024 新特性:v 标志与集合运算
ES2024 引入了 v 标志,对字符类进行重大扩展,支持集合差集、交集操作:
v 标志(Set Notation,ES2024)
启用字符类中的集合运算语法,支持差集(
--)、交集(&&)和嵌套字符类。v 标志与 u 标志不能同时使用,v 是 u 的超集(隐含 Unicode 支持)。// v 标志:字符集差集(Unicode 数字 减去 ASCII 数字) const nonAsciiDigits = /[\p{Decimal_Number}--[0-9]]/v nonAsciiDigits.test('2') // true(全角数字) nonAsciiDigits.test('5') // false(ASCII 数字被排除) // v 标志:字符集交集(ASCII 字母 和 小写字母 的交集) const asciiLower = /[\p{ASCII}&&\p{Lowercase_Letter}]/v asciiLower.test('a') // true asciiLower.test('A') // false asciiLower.test('ä') // false(不是 ASCII) // 嵌套字符类 /[[a-z][A-Z][0-9]]/v // 等价于 [a-zA-Z0-9],但可以用集合语法组合 // 注意:v 标志中的 ] { } 必须转义(v 比 u 更严格) // /[{]/v → 报错,必须用 /[\{]/v 或 /[{{}]/v
WARNING(u 和 v 不能共用)
v 标志是 u 的升级版,不能同时使用 /pattern/uv(会报错)。如果你需要 Unicode 属性转义 \p{...} 和集合运算,使用 v 即可(它自带 Unicode 支持)。另外,v 标志对字符类内的语法更严格:某些在 u 模式下合法的写法在 v 下会报错(如未转义的 ])。
API 对比速查表
| 需求 | Python | JavaScript |
|---|---|---|
| 测试是否匹配 | bool(re.search(p,s)) | /p/.test(s) |
| 获取第一个匹配 | re.search(p,s).group() | s.match(/p/) |
| 获取所有匹配 | re.findall(p,s) | s.match(/p/g) |
| 获取所有匹配+捕获组 | re.finditer(p,s) | s.matchAll(/p/g) |
| 替换 | re.sub(p, r, s) | s.replace(/p/g, r) |
| 替换(函数) | re.sub(p, fn, s) | s.replace(/p/g, fn) |
| 分割 | re.split(p, s) | s.split(/p/) |
| 命名组访问 | m.group('name') | m.groups.name |
| 转义特殊字符 | re.escape(s) | 手动或用库 |
| 预编译 | re.compile(p) | 字面量(自动) |
小结
本章要点
- 字面量
/pattern/flags推荐写法,反斜杠不需要双重转义;动态模式用new RegExp() match()(无 g):返回第一个匹配详情;match(g):返回所有匹配字符串但无捕获组;matchAll(g):返回完整 Match 迭代器(ES2020,推荐)replace():支持$1引用、$<name>命名引用、函数动态替换- lastIndex 陷阱:带
g标志的正则对象有状态;test验证时不要加g,或每次创建新正则 - ES2018+:命名捕获组
(?<name>)、后顾断言、s(dotAll)标志 - Unicode 属性转义
\p{Letter}需要u或v标志;适合多语言文本处理 - ES2024
v标志:支持字符类集合运算(差集--、交集&&),是u的升级版,两者不可共用