正则测试

实时高亮匹配、展示捕获组,支持 g/i/m 开关

正则表达式测试:标志位、贪婪匹配与回溯陷阱

三个标志位在改变什么

标志作用常见误解
g 全局找出所有匹配,而不是只找第一个它有状态,见下一节
i 忽略大小写字母大小写不敏感它同时也让字符类的大小写扩展(如 [a-z] 会匹配大写)
m 多行^ 和 $ 匹配每一行的开头与结尾,而不是整个字符串的首尾它就是让你能"逐行匹配"的开关;不加它 ^ 只匹配全文开头

第三行是最常被忽略的。^ERROR 在不加 m 时只能匹配整个文本的第一行开头;ERROR 出现在第 100 行时不会命中。加上 m 之后,每一行都被当成独立的行来处理。

另外还有一个常被忘记的标志 s(dotAll):让 . 也能匹配换行符。默认情况下 . 不匹配换行,所以想匹配跨行内容时 .* 会失效——这是"为什么我的跨行匹配不工作"的头号原因。

g 标志是有状态的

这是一个会造成"同一个正则时对时错"的真实陷阱。带 g 的正则对象会保留一个内部位置指针(lastIndex):

后果:在循环里对多个字符串复用同一个带 g 的正则时会漏掉匹配。正确做法是每次匹配新建正则对象,或者在复用前手动把 lastIndex 归零。判断依据是"这个模式在单个字符串里出现过一次还是多次"——只判断存在性时不该加 g。

贪婪与懒惰:为什么匹配比预期长

量词默认是贪婪的:尽量多匹配。加上 ? 变成懒惰:尽量少匹配。

输入模式结果
<a>1</a><a>2</a><a>.*</a>整个字符串(贪婪,越过第一个 </a> 继续到最后一个)
同上<a>.*?</a><a>1</a>(懒惰,第一个就停)

提取"每一个标签内容"时必须用懒惰,否则只能拿到第一个到最后一个之间的一大段。但懒惰也会带来回溯代价,见下一节。

灾难性回溯:正则能让浏览器卡死

嵌套量词会产生指数级的回溯。经典例子:用 (a+)+$ 去匹配一长串 a 后面跟一个 b(如 aaaaaaaaaaaaaaaaaaaaaaaaX)。

引擎会尝试所有"如何把 a 分组"的组合,数量随长度指数增长。20 个 a 尚可,30 个就可能让页面无响应。

这类模式在真实项目里的形态通常是:(.*)*、([a-z]+)*、多个 .* 夹着可选部分。防御方式:

常用构造速查

写法含义
\d \w \s数字 / 单词字符 / 空白(大写形式表示取反)
[abc] [^abc]字符集合 / 排除集合
^ $行首 / 行尾(配合 m 时逐行生效)
\b词边界,用来避免 id 命中 userId
(?:...)非捕获组,只分组不占用编号
(?<name>...)命名捕获组
(?=...) (?!...)先行断言:后面必须 / 不能出现某内容
(?<=...) (?<!...)后行断言:前面必须 / 不能出现某内容

字符类内部有两条容易忘的规则:. 在 [] 里就是字面点(不需要转义);而 - 放在开头或结尾也是字面减号,放在中间才表示范围。

正则不适合解决的两类问题

工具支持实时高亮匹配、展示每个匹配及其捕获组内容,并提供 g/i/m 开关,方便逐个验证模式的行为。测试在浏览器本地完成。

常见问题

为什么高亮有时只标了一处?

没勾选 g(全局)时正则只匹配第一个结果。勾选 g 后会高亮所有匹配。

捕获组为空显示什么?

未参与匹配的组会显示为"undefined",这通常发生在可选组未匹配时,属正常现象。