Skip to content

正则表达式

基于现代 JavaScript(ES2025 · 核于 2026-06)

速查

  • 两种创建:字面量 /ab+c/g(解析期编译、性能好、适合常量)vs new RegExp("ab+c", "g")(运行期、适合动态拼接)
  • 八个标志:g 全局 / i 忽略大小写 / m 多行 / s 点匹配换行 / u Unicode / y 粘性 / d 带索引 / v Unicode 集合
  • 字符类:\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)支持集合运算与字符串字面量

两种创建方式

js
// 字面量:在脚本解析时就编译好,性能更优,适合固定模式
const re1 = /ab+c/gi;

// 构造器:运行期编译,适合模式来自变量 / 用户输入
const word = "ab+c";
const re2 = new RegExp(word, "gi");

// 构造器里反斜杠要双重转义(因为先经过字符串解析)
new RegExp("\\d+"); // 等价于 /\d+/

经验:模式写死就用字面量;模式需要拼接(如根据用户输入构造搜索)才用构造器。动态拼接用户输入时,可用 RegExp.escape()(较新)先转义特殊字符防注入。

八个标志位

标志名称作用
gglobal全局匹配,不止找第一个
iignoreCase忽略大小写
mmultiline^ $ 匹配每行的首尾(而非整串首尾)
sdotAll. 也能匹配换行符
uunicode按 Unicode 码点处理,启用 \p{...} 属性转义
ysticky粘性:只从 lastIndex 位置开始匹配
dhasIndices匹配结果附带每个捕获组的起止索引
vunicodeSetsu 的升级版(ES2024),支持集合运算与多码点字符串

标志可组合:/foo/gimsu。标志是正则的固有部分,创建后不能增删

字符类、锚点与量词

js
// 字符类
/\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 标签等场景的关键区别:

js
// 贪婪:<.+> 会一口气吃到最后一个 >
"<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}?

分组与反向引用

js
// 捕获组 (...):括号内匹配的内容会被单独捕获
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(引号成对)

断言:前瞻与后顾

断言检查「某位置前后是否符合某模式」,但不消耗字符(匹配结果不含断言内容):

js
// 正前瞻 (?=...):x 后面必须跟 y
/\d+(?=元)/.exec("100元")[0]; // "100"(匹配数字,但"元"不在结果里)

// 负前瞻 (?!...):x 后面不能跟 y
/\d+(?!元)/; // 数字后面不是"元"

// 正后顾 (?<=...)(ES2018):x 前面必须是 y
/(?<=\$)\d+/.exec("$100")[0]; // "100"(匹配数字,但"$"不在结果里)

// 负后顾 (?<!...):x 前面不能是 y
/(?<!\$)\d+/; // 前面不是"$"的数字

断言常用于「提取被某符号包裹的内容,但又不想要那个符号」——比如取金额数字而不要货币符号。

方法:RegExpString 两套

js
// —— 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):

js
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 副作用

gy 标志的正则对象有可变状态 lastIndex:每次 exec / test 都会推进它,下次从那里继续。这意味着复用同一个全局正则对象会出现「时灵时不灵」的诡异结果:

js
const re = /\d/g;
re.test("1"); // true(lastIndex 推到 1)
re.test("1"); // false(从下标 1 开始找,已到末尾!)

对策:每次新建正则,或循环用 matchAll 而非手动 exec/test 复用。

Unicode 与新特性

u 标志后,正则按 Unicode 码点工作,并解锁 Unicode 属性转义 \p{...}(ES2018):

js
/\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 的模式匹配能力。最常踩的两个坑:带 gmatch 丢捕获组(改用 matchAll)、全局正则的 lastIndex 状态导致复用出错。涉及 emoji 与国际化文本时加 u(或 v)标志按码点处理。下一页进入两类核心集合:Map / Set 与弱引用