正则表达式
基于现代 JavaScript(ES2025 · 核于 2026-06)
速查
- 两种创建:字面量
/ab+c/g(解析期编译、性能好、适合常量)vsnew RegExp("ab+c", "g")(运行期、适合动态拼接) - 八个标志:
g全局 /i忽略大小写 /m多行 /s点匹配换行 /uUnicode /y粘性 /d带索引 /vUnicode 集合 - 字符类:
\d数字 /\w词字符 /\s空白(大写取反);[abc]/[^abc]/[a-z];.任意(除换行) - 锚点:
^行首 /$行尾 /\b词边界 /\B非词边界 - 量词:
*(≥0)/+(≥1)/?(0或1)/{n}/{n,}/{n,m};默认贪婪,加?变懒惰 - 分组:捕获
(...)/ 非捕获(?:...)/ 命名(?<name>...);反向引用\1或\k<name> - 断言:前瞻
(?=...)(?!...)、后顾(?<=...)(?<!...)(不消耗字符) - 方法:
re.test(str)→布尔、re.exec(str)→匹配数组;str.match/matchAll/replace/replaceAll/split/search - 取所有匹配+捕获组:用
str.matchAll(/.../g)(比match+g更强,后者会丢捕获组) g/y的副作用:lastIndex会被exec/test修改,复用同一正则要小心u模式:启用\p{...}Unicode 属性转义;\p{Emoji}/\p{Letter};v(ES2024)支持集合运算与字符串字面量
两种创建方式
// 字面量:在脚本解析时就编译好,性能更优,适合固定模式
const re1 = /ab+c/gi;
// 构造器:运行期编译,适合模式来自变量 / 用户输入
const word = "ab+c";
const re2 = new RegExp(word, "gi");
// 构造器里反斜杠要双重转义(因为先经过字符串解析)
new RegExp("\\d+"); // 等价于 /\d+/经验:模式写死就用字面量;模式需要拼接(如根据用户输入构造搜索)才用构造器。动态拼接用户输入时,可用 RegExp.escape()(较新)先转义特殊字符防注入。
八个标志位
| 标志 | 名称 | 作用 |
|---|---|---|
g | global | 全局匹配,不止找第一个 |
i | ignoreCase | 忽略大小写 |
m | multiline | ^ $ 匹配每行的首尾(而非整串首尾) |
s | dotAll | 让 . 也能匹配换行符 |
u | unicode | 按 Unicode 码点处理,启用 \p{...} 属性转义 |
y | sticky | 粘性:只从 lastIndex 位置开始匹配 |
d | hasIndices | 匹配结果附带每个捕获组的起止索引 |
v | unicodeSets | u 的升级版(ES2024),支持集合运算与多码点字符串 |
标志可组合:/foo/gimsu。标志是正则的固有部分,创建后不能增删。
字符类、锚点与量词
// 字符类
/\d/; // 数字 [0-9]
/\D/; // 非数字
/\w/; // 词字符 [A-Za-z0-9_]
/\W/; // 非词字符
/\s/; // 空白(空格、制表、换行…)
/\S/; // 非空白
/[aeiou]/; // 自定义集合:任一元音
/[^aeiou]/; // 取反:非元音
/[a-z]/; // 范围
/./; // 任意字符(默认不含换行,加 s 标志才含)
// 锚点与边界
/^abc/; // 以 abc 开头
/abc$/; // 以 abc 结尾
/\bword\b/; // word 作为完整单词(两侧是词边界)量词默认贪婪(尽量多匹配),加 ? 变懒惰(尽量少匹配)——这是抓取 HTML 标签等场景的关键区别:
// 贪婪:<.+> 会一口气吃到最后一个 >
"<a><b>".match(/<.+>/)[0]; // "<a><b>"
// 懒惰:<.+?> 匹配到第一个 > 就停
"<a><b>".match(/<.+?>/)[0]; // "<a>"| 量词 | 含义 |
|---|---|
x* | 0 个或多个 |
x+ | 1 个或多个 |
x? | 0 个或 1 个 |
x{n} | 恰好 n 个 |
x{n,} | 至少 n 个 |
x{n,m} | n 到 m 个 |
加后缀 ? | 改为懒惰(*? +? {n,m}?) |
分组与反向引用
// 捕获组 (...):括号内匹配的内容会被单独捕获
const m = "2026-06-25".match(/(\d{4})-(\d{2})-(\d{2})/);
m[0]; // "2026-06-25"(整体匹配)
m[1]; // "2026"(第 1 组)
m[2]; // "06"(第 2 组)
// 非捕获组 (?:...):只分组不捕获,省内存、不占编号
"abab".match(/(?:ab)+/)[0]; // "abab"
// 命名捕获组 (?<name>...)(ES2018):用名字取,可读性大增
const d = "2026-06-25".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
d.groups.year; // "2026"
d.groups.month; // "06"
// 反向引用:\1 引用第 1 个捕获组的内容(匹配重复)
/(\w)\1/.test("aa"); // true(同一字符出现两次)
/(\w)\1/.test("ab"); // false
// 命名反向引用 \k<name>
/(?<q>['"]).*?\k<q>/.test(`"hi"`); // true(引号成对)断言:前瞻与后顾
断言检查「某位置前后是否符合某模式」,但不消耗字符(匹配结果不含断言内容):
// 正前瞻 (?=...):x 后面必须跟 y
/\d+(?=元)/.exec("100元")[0]; // "100"(匹配数字,但"元"不在结果里)
// 负前瞻 (?!...):x 后面不能跟 y
/\d+(?!元)/; // 数字后面不是"元"
// 正后顾 (?<=...)(ES2018):x 前面必须是 y
/(?<=\$)\d+/.exec("$100")[0]; // "100"(匹配数字,但"$"不在结果里)
// 负后顾 (?<!...):x 前面不能是 y
/(?<!\$)\d+/; // 前面不是"$"的数字断言常用于「提取被某符号包裹的内容,但又不想要那个符号」——比如取金额数字而不要货币符号。
方法:RegExp 与 String 两套
// —— RegExp 上的方法 ——
/\d+/.test("abc123"); // true(只问"匹配不匹配",最快)
const re = /(\d)(\d)/;
re.exec("a12"); // ["12", "1", "2", index: 1, ...](带捕获组与位置)
// —— String 上的方法 ——
"a1b2".match(/\d/g); // ["1", "2"](带 g:所有匹配,但丢捕获组)
"a1b2".search(/\d/); // 1(首个匹配的下标,找不到 -1)
"a1b2".replace(/\d/g, "#"); // "a#b#"
"a1b2".replaceAll(/\d/g, "#"); // "a#b#"(传正则须带 g)
"a1b2".split(/\d/); // ["a", "b", ""]match vs matchAll:取所有匹配+捕获组的正解
match 在带 g 标志时只返回匹配字符串数组、丢掉所有捕获组。要同时拿到「所有匹配」和「每个匹配的捕获组」,必须用 matchAll(ES2020,返回迭代器,要求带 g):
const text = "2026-06, 2025-12";
const re = /(?<y>\d{4})-(?<m>\d{2})/g;
// ❌ match + g:丢了捕获组
text.match(re); // ["2026-06", "2025-12"](拿不到 y / m)
// ✅ matchAll:每个匹配都是完整的 exec 结果,带捕获组
for (const m of text.matchAll(re)) {
console.log(m[0], m.groups.y, m.groups.m);
}
// "2026-06" "2026" "06"
// "2025-12" "2025" "12"g / y 标志的 lastIndex 副作用
带 g 或 y 标志的正则对象有可变状态 lastIndex:每次 exec / test 都会推进它,下次从那里继续。这意味着复用同一个全局正则对象会出现「时灵时不灵」的诡异结果:
const re = /\d/g;
re.test("1"); // true(lastIndex 推到 1)
re.test("1"); // false(从下标 1 开始找,已到末尾!)对策:每次新建正则,或循环用 matchAll 而非手动 exec/test 复用。
Unicode 与新特性
加 u 标志后,正则按 Unicode 码点工作,并解锁 Unicode 属性转义 \p{...}(ES2018):
/\p{Letter}/u.test("中"); // true(按 Unicode 类别匹配"字母")
/\p{Emoji}/u.test("😀"); // true
/\p{Number}/u.test("Ⅻ"); // true(连罗马数字也算 Number)
// 不加 u,. 会把一个 emoji 当成两个码元
/^.$/.test("😀"); // false(被当 2 个字符)
/^.$/u.test("😀"); // true(u 模式按码点)v 标志(unicodeSets,ES2024)是 u 的超集,支持字符类内的集合运算(交集 &&、差集 --)与多码点字符串字面量,是处理复杂 Unicode 匹配的最新利器——目前在主流浏览器已广泛可用,作为锦上添花使用。
小结
正则两种创建方式、八个标志、捕获/命名组、断言,构成 JavaScript 的模式匹配能力。最常踩的两个坑:带 g 时 match 丢捕获组(改用 matchAll)、全局正则的 lastIndex 状态导致复用出错。涉及 emoji 与国际化文本时加 u(或 v)标志按码点处理。下一页进入两类核心集合:Map / Set 与弱引用。