正则表达式测试

用自己的文本测试 JavaScript 正则表达式。可切换 g、i、m、s、u 标志,查看每个匹配的位置与捕获组,并实时预览替换结果。

边输入边看到每一个匹配、每一个分组和每一个位置。 输入模式和待匹配的文本。每条结果都会列出位置和捕获组;模式非法时会显示引擎自身的报错信息;替换框则精确展示一次 replace 调用会产生什么。

/ / g
匹配结果

像引擎那样读一条正则表达式

标志改变的是含义,而不只是搜索方式

这五个标志不是偏好设置,它们会重新定义整个模式。没有 g 时,搜索找到第一个匹配就停止;加上它,引擎会维护一个 lastIndex 游标遍历整个字符串——这也是同一个全局正则在多次调用间复用时,看起来会「跳过」匹配的原因。i 标志按 Unicode 大小写映射折叠大小写,而不是简单的 ASCII 位移。

最容易被误解的是 msm 只改变 ^$ 的含义,把它们锚定到行边界而非整个字符串,对 . 毫无影响;反过来 s 只改变 .,让它能匹配换行符。记住 "dotall" 这个名字最有用。u 标志让模式按码点而非 UTF-16 码元工作,于是 . 能把一个 emoji 当作一个整体匹配,\u{1F600} 这样的转义也变得合法。它同时会把过去被容忍的转义变成错误——这就是给已有模式加上它会突然报错的原因。

分组、位置与替换

每一对圆括号默认都会捕获,按左括号出现的顺序编号,替换时的 $1$2 引用的正是这些编号。嵌套遵循同样的规则,所以在 ((a)b) 中第一组是 ab,第二组是 a。当你只是为了分支或重复而需要分组时,用 (?:...) 可以不分配捕获,也让编号更易读。

每个匹配所报告的位置,是首个被匹配字符的偏移量,其意义比听上去更大。它让你能区分位于不同位置的两个相同匹配,确认锚点是否如预期生效,并诊断经典的零长度匹配。像 \d* 这样的模式在任何地方都能匹配空串,于是全局扫描会在每个位置都找到一个匹配;引擎之所以还能推进,是因为实现会在匹配为空时强制把游标前移。

回溯与匹配上限

包括 JavaScript 在内的多数引擎使用回溯。当模式的某一部分失败时,引擎会退回上一个决策点尝试另一条路径。通常这不可见。一旦嵌套量词让路径数量指数级增长,它就会变成灾难,经典形态是用 (a+)+b 去匹配一长串没有 ba。这种输入能让浏览器标签页卡死;如果模式来自用户输入,它就成了拒绝服务的攻击面。

两个习惯几乎能规避全部问题:优先使用具体的字符类而非 .*,以免多个分支互相重叠;能加锚点就加锚点,因为带锚点的失败会被立即否定,而不是在每个偏移位置重试一遍。本工具还把结果上限设为 500 条并在截断时给出提示,这样失控的模式只会退化为较慢的结果,而不是冻结页面。

开源说明:使用原生 JavaScript 实现,不依赖第三方库。

常见问题

这里用的是哪种正则方言?
JavaScript,也就是浏览器内置的 ECMAScript 引擎。Perl、PCRE 和 Python 的写法接近,但在后行断言、命名分组和部分转义上有差异。
g 标志到底改变了什么?
不加时只返回第一个匹配;加上后引擎会扫描整个字符串并返回全部匹配,这里的结果列表正是这样填充的。
为什么会出现空匹配?
因为你的模式可以匹配零个字符,例如 `\d*`。全局扫描于是在每个位置都报告一个匹配,引擎通过手动前移游标来避免死循环。
替换时如何引用捕获组?
用 $1 引用第一组、$2 引用第二组,$& 表示整个匹配。若需要字面的美元符号,请写 $$。
「仅显示前 500 个」是什么意思?
结果列表上限为 500 条,避免过于宽泛的模式冻结页面。替换预览仍然作用于全文。
为什么我的模式让页面变卡?
像 (a+)+ 这样的嵌套量词会导致指数级回溯。请用具体字符类替代 .*,并给模式加上锚点,让匹配保持线性。