土薯工具 Toolshu.com 登录 用户注册

正则表达式 lookahead 和 lookbehind 到底怎么用?

作者:bhnw 于 2026-05-01 17:30 发布 2次浏览 收藏 (0)

会写基础正则之后,迟早会遇到一种需求:我想匹配某个内容,但只在它前面或后面有特定字符的时候才匹配——而且那个特定字符不应该出现在匹配结果里。

这就是 lookahead 和 lookbehind 要解决的问题。


先理解一件事:lookaround 不消耗字符

普通的正则匹配,每匹配一个字符,引擎的位置就往前移一格。比如匹配 ab,引擎匹配了 a,位置移到 b,再匹配 b,位置移到 b 之后。

lookaround 不一样。它只是"往前看"或"往后看",确认有没有某个模式,但不移动位置、不消耗字符、不把结果包含在匹配结果里

这是理解所有 lookaround 的核心。


四种语法

类型 语法 含义
正向先行断言 X(?=Y) 匹配 X,但只在 X 后面跟着 Y 的时候
负向先行断言 X(?!Y) 匹配 X,但只在 X 后面不跟着 Y 的时候
正向后行断言 (?<=Y)X 匹配 X,但只在 X 前面有 Y 的时候
负向后行断言 (?<!Y)X 匹配 X,但只在 X 前面没有 Y 的时候

记忆方法:= 是正向(存在),! 是负向(不存在);(?= 往右看,(?<= 往左看。


正向先行断言:(?=...)

场景:从 30€ 里只匹配数字,不要匹配到欧元符号。

const str = "苹果 5 元,橙子 30€,香蕉 15$";
str.match(/\d+(?=€)/g);
// ["30"]
// "30" 匹配了,但 € 不在结果里——因为 (?=€) 只是断言,不消耗字符

负向先行断言:(?!...)

场景:匹配 http,但不匹配 https

const str = "访问 http://example.com 或 https://secure.com";
str.match(/http(?!s)/g);
// ["http"]  只匹配了第一个

场景:过滤掉 .min.js 文件。

const files = ["app.js", "app.min.js", "utils.js", "vendor.min.js"];
files.filter(f => /(?<!\.min)\.js$/.test(f));
// ["app.js", "utils.js"]

正向后行断言:(?<=...)

场景:只匹配美元符号后面的数字。

const str = "商品 A: $30,数量: 5,商品 B: $120";
str.match(/(?<=\$)\d+/g);
// ["30", "120"]  只有 $ 后面的数字,5 没有被匹配到

场景:提取 CSS 变量的颜色值。

const css = "--primary-color: #3498db;";
css.match(/(?<=:\s*)#[0-9a-f]+/i);
// ["#3498db"]

负向后行断言:(?<!...)

场景:匹配没有被转义的引号(前面没有反斜杠)。

const str = String.raw`他说 \"hello\" 和 "world"`;
str.match(/(?<!\\)"/g);
// 只匹配 "world" 前后的引号

场景:过滤掉 .min.js 文件。

const files = ["app.js", "app.min.js", "utils.js"];
files.filter(f => /(?<!\.min)\.js$/.test(f));
// ["app.js", "utils.js"]

实用场景:多个 lookahead 组合验证密码

这是 lookahead 最经典的用法——用多个断言同时验证多个条件,每个断言从同一个位置开始检查,互不干扰。

// 密码必须:至少8位,含大写、小写、数字、特殊字符
const passwordRegex = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/;

passwordRegex.test("weak");           // false
passwordRegex.test("StrongPass1!");   // true
passwordRegex.test("nouppercase1!");  // false(没有大写)

为什么用 (?=.*[A-Z]) 而不是直接写 [A-Z]?因为大写字母可以在密码任意位置,.* 让断言跳过任意字符去找,而断言不消耗字符,所以下一个断言还是从字符串开头出发。每个断言都从同一个位置出发——这是多个 lookahead 组合的关键。


JavaScript 的 lookbehind 兼容性

lookbehind((?<=...)(?<!...))是 ES2018 才加入 JavaScript 的,IE 完全不支持,Safari 要 14.1 以上。

如果需要兼容老浏览器,改用捕获组:

// lookbehind 写法(ES2018+)
"价格: $30".match(/(?<=\$)\d+/);  // ["30"]

// 替代写法:捕获组
const match = "价格: $30".match(/\$(\d+)/);
const price = match ? match[1] : null;  // "30"

lookahead((?=...)(?!...))所有现代浏览器都支持,没有兼容性问题。


Python 的 lookbehind 不支持可变长度

import re

# Python 的 lookbehind 只支持固定宽度
re.findall(r'(?<=a+)b', 'aaab')
# error: look-behind requires fixed width pattern

re.findall(r'(?<=a)b', 'aab')   # 固定1个a,OK
re.findall(r'(?<=aa)b', 'aab')  # 固定2个a,OK

JavaScript(ES2018+)支持可变长度 lookbehind,Python 内置的 re 不支持。需要可变长度 lookbehind 的话,用 PyPI 上的 regex 库。


调试 lookaround 最直观的方式是实时可视化——把正则粘到 正则表达式可视化工具 里,断言的匹配范围和结果一目了然,比在脑子里模拟引擎行为省事多了。

发现周边 发现周边
评论区

加载中...