正则零宽断言(Lookahead / Lookbehind)
零宽断言匹配"位置条件"而非字符。包括正向/负向先行断言和正向/负向后行断言,用于精确限定匹配上下文。
#type / concept
#status / evergreen
#resource / javascript
#resource / ecmascript
[!info] related notes
- 所属 MOC: ecmascript-moc
- 前置概念: ecmascript-regular-expressions
- 并列概念: regex-greedy-vs-non-greedy, regex-lastindex-pitfall
- 语法速查: regex
正则零宽断言(Lookahead / Lookbehind)
一句话定义
断言匹配的是”位置条件”,不消耗字符。它只检查”当前位置的前/后是否满足某条件”,但不会把检查的部分纳入匹配结果。
四种断言
| 断言 | 名称 | 含义 |
|---|---|---|
(?=...) | 正向先行 | 当前位置后面必须跟 ... |
(?!...) | 负向先行 | 当前位置后面不能跟 ... |
(?<=...) | 正向后行 | 当前位置前面必须跟 ... |
(?<!...) | 负向后行 | 当前位置前面不能跟 ... |
“先行”看右边,“后行”看左边。
实际例子
正向先行 (?=...)
匹配后面紧跟 px 的数字(不含 px):
"12px 30em 8px".match(/\d+(?=px)/g);
// ["12", "8"]
密码强度校验(同时满足多个条件):
// 必须包含大写、小写、数字,且至少 8 位
const reg = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$/;
reg.test("Abc12345"); // true
reg.test("abc12345"); // false(没大写)
reg.test("ABCDEFG1"); // false(没小写)
负向先行 (?!...)
匹配后面不是 px 的数字:
"12px 30em 8px".match(/\d+(?!px)/g);
// 注意:这会匹配到每个数字中"后面不是 px"的那个位置
// 实际使用中通常需要更精确的写法
更实用的场景:排除特定后缀
// 匹配不以 .min.js 结尾的文件名
"app.js app.min.js utils.js".match(/\w+(?!\.min)\.js/g);
// ["app.js", "utils.js"]
正向后行 (?<=...)
匹配美元符号后面的数字(不含 $):
"$199 ¥200 €300".match(/(?<=\$)\d+/g);
// ["199"]
匹配 color: 后面的值:
const str = "color: red; background: blue;";
str.match(/(?<=color:\s*)\w+/g);
// ["red"]
负向后行 (?<!...)
匹配前面不是 $ 的数字:
"$199 200 €300".match(/(?<!\$)\d+/g);
// 注意:会匹配数字中每个"前面不是 $" 的位置
更实用:匹配不在引号内的内容
// 匹配不在 HTML 标签内的文本(简化示例)
const str = "<b>bold</b> normal <i>italic</i>";
// 匹配不在 < > 之间的单词
str.match(/(?<=>)[^<]+/g);
// ["bold", " normal ", "italic"]
组合使用
// 匹配 "hello" 但仅当它在 "say" 后面且不在 "goodbye" 前面
const reg = /(?<=\bsay\s)hello(?!\s+goodbye)/;
reg.test("say hello"); // true
reg.test("say hello goodbye"); // false
常见场景
提取特定格式的值
// 从 CSS 中提取所有颜色值
const css = "color: #fff; background: #333; border: 1px solid #aaa";
css.match(/(?<=#)[0-9a-f]{3,6}/gi);
// ["fff", "333", "aaa"]
替换特定上下文中的内容
// 只替换冒号后面的数字(日期部分)
"2026:03:21".replace(/(?<=:)\d{2}/g, (m) => `[${m}]`);
// "2026:[03]:[21]"
条件过滤
// 匹配以 "http" 开头但不是 "https" 的 URL
const urls = "http://a.com https://b.com http://c.com";
urls.match(/http(?!s):\/\/[^\s]+/g);
// ["http://a.com", "http://c.com"]
注意事项
后行断言在老环境中不支持
正向先行和负向先行支持很广泛(ES5+)。
正向后行 (?<=...) 和负向后行 (?<!...) 是 ES2018 才正式标准化的,老浏览器和 Node 8 以下可能不支持。
断言不消耗字符
"12px".match(/\d+(?=px)/);
// 匹配结果是 "12",不是 "12px"
// 断言只检查位置,不纳入匹配内容
不能替代非贪婪
断言和非贪婪解决的问题不同:
- 非贪婪:控制量词”吃多少”
- 断言:限定匹配必须满足的”前后位置条件”
两者经常配合使用:
// 匹配 <div> 标签内的内容(不含标签本身)
const str = "<div>hello</div>";
const content = str.match(/(?<=<div>).*?(?=<\/div>)/);
// ["hello"]
速查
(?=...) // 后面跟 ...
(?!...) // 后面不跟 ...
(?<=...) // 前面跟 ...
(?<!...) // 前面不跟 ...