先行斷言和後行斷言
後行斷言
JavaScript 語言的正則表達式,只支持先行斷言(lookahead)和先行否定斷言(negative lookahead),不支持後行斷言(lookbehind)和後行否定斷言(negative lookbehind)。目前,有一個提案,引入後行斷言,V8 引擎 4.9 版已經支持。
”先行斷言“指的是,x
只有在y
前面才匹配,必須寫成/x(?=y)/
。比如,只匹配百分號之前的數字,要寫成/\d+(?=%)/
。”先行否定斷言“指的是,x
只有不在y
前面才匹配,必須寫成/x(?!y)/
。比如,只匹配不在百分號之前的數字,要寫成/\d+(?!%)/
。
/\d+(?=%)/.exec(‘100% of US presidents have been male‘) // ["100"]
/\d+(?!%)/.exec(‘that’s all 44 of them‘) // ["44"]
上面兩個字符串,如果互換正則表達式,就不會得到相同結果。另外,還可以看到,”先行斷言“括號之中的部分((?=%)
),是不計入返回結果的。
“後行斷言”正好與“先行斷言”相反,x
只有在y
後面才匹配,必須寫成/(?<=y)x/
。比如,只匹配美元符號之後的數字,要寫成/(?<=\$)\d+/
。”後行否定斷言“則與”先行否定斷言“相反,x
只有不在y
後面才匹配,必須寫成/(?<!y)x/
。比如,只匹配不在美元符號後面的數字,要寫成/(?<!\$)\d+/
。
/(?<=\$)\d+/.exec(‘Benjamin Franklin is on the $100 bill‘) // ["100"]
/(?<!\$)\d+/.exec(‘it’s is worth about €90‘) // ["90"]
上面的例子中,“後行斷言”的括號之中的部分((?<=\$)
),也是不計入返回結果。
下面的例子是使用後行斷言進行字符串替換。
const RE_DOLLAR_PREFIX = /(?<=\$)foo/g;
‘$foo %foo foo‘.replace(RE_DOLLAR_PREFIX, ‘bar‘);
// ‘$bar %foo foo‘
上面代碼中,只有在美元符號後面的foo
才會被替換。
“後行斷言”的實現,需要先匹配/(?<=y)x/
的x
,然後再回到左邊,匹配y
的部分。這種“先右後左”的執行順序,與所有其他正則操作相反,導致了一些不符合預期的行為。
首先,”後行斷言“的組匹配,與正常情況下結果是不一樣的。
/(?<=(\d+)(\d+))$/.exec(‘1053‘) // ["", "1", "053"]
/^(\d+)(\d+)$/.exec(‘1053‘) // ["1053", "105", "3"]
上面代碼中,需要捕捉兩個組匹配。沒有"後行斷言"時,第一個括號是貪婪模式,第二個括號只能捕獲一個字符,所以結果是105
和3
。而"後行斷言"時,由於執行順序是從右到左,第二個括號是貪婪模式,第一個括號只能捕獲一個字符,所以結果是1
和053
。
其次,"後行斷言"的反斜杠引用,也與通常的順序相反,必須放在對應的那個括號之前。
/(?<=(o)d\1)r/.exec(‘hodor‘) // null
/(?<=\1d(o))r/.exec(‘hodor‘) // ["r", "o"]
上面代碼中,如果後行斷言的反斜杠引用(\1
)放在括號的後面,就不會得到匹配結果,必須放在前面才可以。因為後行斷言是先從左到右掃描,發現匹配以後再回過頭,從右到左完成反斜杠引用。
先行斷言和後行斷言