先记住这个答案
前瞻与后顾是零宽断言:它们在当前位置向左或向右探查是否满足某个模式,满足才允许主模式继续匹配,但探查到的内容不进结果。x(?=y) 要求 x 后面是 y,x(?!y) 要求后面不是 y;(?<=y)x 要求 x 前面是 y,(?<!y)x 要求前面不是 y。前瞻是所有 JavaScript 引擎长期支持的语法,后顾是 ES2018 才加入的,老环境解析含 (?<=) 的正则字面量就会抛 SyntaxError,因此涉及后顾时要先确认运行环境,或用 RegExp 构造器加 try/catch 做降级。
- 四种断言零宽:只检查不进匹配结果
- 前瞻全支持,后顾需 ES2018
- 旧环境用后顾字面量直接抛 SyntaxError
- 需要否定条件时优先断言而非复杂字符类
零宽断言如何参与匹配
匹配引擎扫描到某个位置时,断言会在该位置做一次子匹配测试。x(?=y) 表示:先正常匹配 x,然后在 x 结束的位置测试后面能否匹配 y,测试通过则整个匹配成功,但 y 的字符不会被消费,下一次匹配仍可从该位置继续。否定形式 x(?!y) 逻辑相反,要求后面不能匹配 y。
后顾断言方向相反:(?<=y)x 要求当前位置之前刚出现过 y,引擎从当前位置往回检查。因为 JavaScript 字符串索引天然从左到右,后顾需要引擎反向推演,实现成本更高,所以它直到 ES2018 才标准化,且历史上只允许定长模式的规定逐步放宽。两类断言的共同点是不产生捕获、不占用匹配文本。
从键值日志中提取等号后的数字
日志解析任务:输入形如 a=1,b=2 的键值串,只要等号后的数字本身,不要等号,也不要键名。约束是不能引入捕获组、结果直接来自 match。决策:用 /(?<==)\d+/g,后顾断言确认数字前面是等号,匹配结果只含数字。对 a=1,b=2 得到两个数字,对以等号开头的 =9 也能正确取到 9。
选择后顾而不是 =(\d+) 加捕获组,是因为题设要求返回干净的数字数组,match 加 g 标志时只返回整体匹配,捕获组内容拿不到。用断言把等号排除在匹配之外,结果即所得。若环境不允许后顾,就退化为先匹配 =(\d+) 再取分组,或用 replace 剥离前缀,代码更啰嗦但行为等价。
const pattern = /(?<=\=)\d+/g;
const samples = ["a=1,b=2", "=9", "x=12 y=34"];
for (const s of samples) {
console.log(JSON.stringify(s.match(pattern)));
}查看输出与解释
["1","2"]
["9"]
["12","34"]需要 ES2018 及以上环境,例如现代浏览器或 Node 8.3 之后的版本;更老的环境解析该字面量会直接抛 SyntaxError。
后顾断言的兼容性与降级边界
最容易踩的坑是把含 (?<=) 的正则字面量写进需要支持旧浏览器的代码:这不是运行到该行才失败,而是整个脚本在解析阶段就抛 SyntaxError,后面所有代码都不会执行。前瞻断言没有这个问题,它从早期 JavaScript 就存在,可放心使用。
可行降级有两个:一是改用 new RegExp("(?<=x)y") 构造器并包 try/catch,失败时回退到不用后顾的备用正则,把语法错误推迟为可捕获的运行时错误;二是干脆不用后顾,用 match 加索引判断前一个字符,或用捕获组取目标部分。代价是代码更长、意图不如断言直观,但能覆盖所有环境。
容易答错的地方
- 以为断言内容会出现在匹配结果里
- 断言是零宽的,
/\w+(?= fox)/对A quick fox匹配到的只是quick,fox只是条件,不进结果也不影响下一次匹配起点。 - 认为后顾断言和老语法一样安全
- 后顾是 ES2018 语法,旧引擎对含它的正则字面量直接抛语法错误且无法 try/catch 字面量本身。需要兼容时改用构造器动态创建或放弃后顾写法。
面试官还会怎么问?
前瞻断言里能再嵌套断言吗?
可以,断言内部是完整子模式,能再包含前瞻甚至后顾,例如 (?=\d{3}(?!\d))。但嵌套会显著降低可读性,复杂条件建议拆成多个正则或分步判断。
如何检测当前环境是否支持后顾断言?
用 try/catch 包裹 new RegExp("(?<=a)b"),不抛错即支持。必须走构造器,因为检测代码若直接写字面量,不支持的环境会在脚本加载时崩溃,检测本身都执行不到。
否定后顾 `(?<!y)` 常见用途是什么?
典型场景是排除带特定前缀的目标,如 /(?<!-)\d+/ 只匹配前面没有减号的数字,避免匹配到负数的绝对值部分。注意它同样要求 ES2018 环境。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。