土薯工具 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 最直觀的方式是實時可視化——把正則粘到 正則表達式可視化工具 裏,斷言的匹配範圍和結果一目瞭然,比在腦子裏模擬引擎行爲省事多了。

发现周边 发现周边
評論區

加載中...