會寫基礎正則之後,遲早會遇到一種需求:我想匹配某個內容,但只在它前面或後面有特定字符的時候才匹配——而且那個特定字符不應該出現在匹配結果裏。
這就是 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 最直觀的方式是實時可視化——把正則粘到 正則表達式可視化工具 裏,斷言的匹配範圍和結果一目瞭然,比在腦子裏模擬引擎行爲省事多了。



加載中...