先行斷言:(?=...), (?!...)
先行斷言會“向前檢視”:它試圖用給定的模式匹配後續輸入,但它不消耗任何輸入——如果匹配成功,輸入中的當前位置保持不變。
語法
(?=pattern)
(?!pattern)
引數
描述
正則表示式通常從左到右匹配。這就是為什麼先行斷言和後行斷言被稱為先行和後行斷言——先行斷言判斷右側的內容,後行斷言判斷左側的內容。
為了使 (?=pattern) 斷言成功,pattern 必須匹配當前位置之後的文字,但當前位置不會改變。(?!pattern) 形式否定了斷言——如果 pattern 在當前位置不匹配,則斷言成功。
pattern 可以包含捕獲組。有關此情況下的行為的更多資訊,請參閱捕獲組頁面。
與其他正則表示式運算子不同,先行斷言不會進行回溯——此行為繼承自 Perl。這僅在 pattern 包含捕獲組,並且先行斷言之後的模式包含對這些捕獲的反向引用時才重要。例如:
/(?=(a+))a*b\1/.exec("baabac"); // ['aba', 'a']
// Not ['aaba', 'a']
上述模式的匹配過程如下:
- 先行斷言
(a+)在"baabac"中的第一個"a"之前成功,並且"aa"被捕獲,因為量詞是貪婪的。 a*b匹配"baabac"中的"aab",因為先行斷言不消耗它們匹配的字串。\1不匹配後面的字串,因為那需要兩個"a",但只有一個可用。因此匹配器回溯,但它不進入先行斷言,所以捕獲組不能減少到一個"a",並且整個匹配在此點失敗。exec()在下一個位置(第二個"a"之前)重新嘗試匹配。這次,先行斷言匹配"a",a*b匹配"ab"。反向引用\1匹配捕獲的"a",匹配成功。
如果正則表示式能夠回溯到先行斷言並修改那裡的選擇,那麼匹配將在步驟 3 中透過 (a+) 匹配第一個 "a"(而不是前兩個 "a")和 a*b 匹配 "aab" 而成功,甚至無需重新嘗試下一個輸入位置。
否定先行斷言也可以包含捕獲組,但反向引用只在 pattern 內部才有意義,因為如果匹配繼續,pattern 必然是不匹配的(否則斷言會失敗)。這意味著在 pattern 之外,對否定先行斷言中那些捕獲組的反向引用總是成功。例如:
/(.*?)a(?!(a+)b\2c)\2(.*)/.exec("baaabaac"); // ['baaabaac', 'ba', undefined, 'abaac']
上述模式的匹配過程如下:
(.*?)模式是非貪婪的,所以它開始時不匹配任何東西。然而,下一個字元是a,它在輸入中未能匹配"b"。(.*?)模式匹配"b",以便模式中的a匹配"baaabaac"中的第一個"a"。- 在此位置,先行斷言成功匹配,因為如果
(a+)匹配"aa",那麼(a+)b\2c匹配"aabaac"。這導致斷言失敗,因此匹配器回溯。 (.*?)模式匹配"ba",以便模式中的a匹配"baaabaac"中的第二個"a"。- 在此位置,先行斷言匹配失敗,因為剩餘的輸入不符合模式“任意數量的
"a"、一個"b"、相同數量的"a"、一個c”。這導致斷言成功。 - 然而,由於斷言中沒有任何內容被匹配,
\2反向引用沒有值,因此它匹配空字串。這導致剩餘的輸入被末尾的(.*)消耗。
通常,斷言不能被量化。然而,在Unicode-不感知模式下,先行斷言可以被量化。這是為了網路相容性而廢棄的語法,您不應依賴它。
/(?=a)?b/.test("b"); // true; the lookahead is matched 0 time
示例
匹配字串而不消耗它們
有時,驗證匹配的字串後面跟著某些內容而不將它們作為結果返回很有用。以下示例匹配後面跟著逗號/句點的字串,但標點符號不包含在結果中:
function getFirstSubsentence(str) {
return /^.*?(?=[,.])/.exec(str)?.[0];
}
getFirstSubsentence("Hello, world!"); // "Hello"
getFirstSubsentence("Thank you."); // "Thank you"
透過捕獲你感興趣的子匹配,也可以達到類似的效果。
模式減法和交集
使用先行斷言,你可以用不同的模式多次匹配一個字串,這允許你表達複雜的減法(是 X 但不是 Y)和交集(既是 X 也是 Y)關係。
以下示例匹配任何不是保留字的識別符號(為簡潔起見,此處僅顯示三個保留字;更多保留字可以新增到此或運算子中)。[$_\p{ID_Start}][$\p{ID_Continue}]* 語法精確描述了語言規範中的識別符號字串集;你可以在詞法語法中閱讀更多關於識別符號的資訊,並在Unicode 字元類轉義中閱讀關於 \p 轉義的資訊。
function isValidIdentifierName(str) {
const re = /^(?!(?:break|case|catch)$)[$_\p{ID_Start}][$\p{ID_Continue}]*$/u;
return re.test(str);
}
isValidIdentifierName("break"); // false
isValidIdentifierName("foo"); // true
isValidIdentifierName("cases"); // true
以下示例匹配既是 ASCII 又可以用作識別符號部分的字串:
function isASCIIIDPart(char) {
return /^(?=\p{ASCII}$)\p{ID_Start}$/u.test(char);
}
isASCIIIDPart("a"); // true
isASCIIIDPart("α"); // false
isASCIIIDPart(":"); // false
如果你對有限數量的字元進行交集和減法操作,你可能希望使用帶有 v 標誌啟用的字元集交集語法。
規範
| 規範 |
|---|
| ECMAScript® 2026 語言規範 # prod-Assertion |
瀏覽器相容性
載入中…