正則表達式中,有一個繞不過去的坎,那就是零寬斷言
css
零寬斷言
是一種零寬度的匹配,它匹配的內容不會保存到匹配結果中,也不會佔用index
寬度,最終匹配的結果只是一個位置html
簡單的說,它用於查找在某些內容以前或以後的東西(但返回結果並不包括這些內容)正則表達式
JavaScript
中只支持零寬先行斷言
工具
零寬斷言
分爲4
類學習
正向零寬先行斷言(?=exp
)測試
exp
這個表達式負向零寬先行斷言(?!exp
).net
exp
這個表達式正向零寬後發斷言(?<=exp
)code
exp
這個表達式負向零寬後發斷言(?<!exp
)regexp
exp
這個表達式注,關於先行
和後發
,還有其它稱呼,譬如前瞻
和後瞻
等,本文統一使用先行
與後發
htm
JavaScript
中的斷言JavaScript
語言內只支持零寬先行斷言
(即只支持?=exp
和?!exp
)
因此本文中只會介紹零寬先行斷言
另外,能夠經過RegexBuddy 4
等工具分析正則的匹配過程
示例1
var str="abcdefg"; var reg=/ab(?=cd)/; str.match(reg); // ["ab", index: 0, input: "abcdefg"]
index = 0
,a
匹配a
成功,嘗試b
匹配b
成功(?=cd)
接管控制權(?=cd)
依次嘗試匹配c
和d
成功示例2
var str="abcdefg"; var reg=/(?=cd)efg/; str.match(reg); // null
想要達到的效果是匹配在cd
後方的efg
,可是這是零寬後發斷言
纔有的效果(?<=exp
),而JS
中並不支持,此時使用先行斷言,實際效果爲
?=cd
獲取控制權,一直到index = 2
時才匹配成功,接下來e
獲取控制權?=cd
是零寬式的,所以匹配成功後,下一輪匹配依然從index = 2
開始嘗試,此時c
匹配e
失敗,因而index
挪到3
d, e, f, g
匹配?=cd
失敗,因而最終匹配失敗,返回null
示例3
var str="abcdefg"; var reg=/(?=cd)cdefg/; str.match(reg); // ["cdefg", index: 2, input: "abcdefg"]
基於示例2的變形
index = 2
時,?=cd
匹配成功了,交給cdefg
index = 2
,此時恰好c
匹配,繼續吃進d
,e
,f
,g
也都匹配,因而匹配成功,因而返回成功結果示例4
var str="abcdefg"; var reg=/ab(?=cd)cdefg/; str.match(reg); // ["abcdefg", index: 0, input: "abcdefg"]
上述示例的綜合
index = 0
時,左側的ab
匹配成功index = 2
處),?=cd
也匹配成功index = 2
開始嘗試,c, d, e ,f , g
依次匹配成功,因而匹配結束,返回成功結果(index = 0
,由於沒有失敗,後續的嘗試都成功了)注,零寬斷言返回的是位置而不是字符,零寬斷言匹配成功後,其他表達式會基於這個返回的位置繼續判斷
另外,請不要把先行斷言
當成後發斷言
來用
示例1
var str="abcdefg"; var reg=/ab(?!cd)/; str.match(reg); // null
ab
匹配成功後,接下來cd
匹配?!cd
失敗b, c, d, e, f, g
依次都匹配a
失敗,因而最終匹配失敗,返回null
示例2
var str="abcdefg"; var reg=/ab(?!ab)cd/; str.match(reg); // ["abcd", index: 0, input: "abcdefg"]
ab
匹配成功後,接下來cd
匹配?!ab
成功?!ab
是零寬的,所以接下來仍然從index = 2
處嘗試(也就是c
繼續匹配c
),所以匹配成功,接下來d
也匹配d
成功,全部表達式匹配完畢,最終返回成功結果(index = 0
,由於沒有失敗)接下來一些實戰練習,加深印象
ing
單詞的前綴需求說明
例如: I am reading in the dining room
的匹配結果應該是read
與din
代碼
var str="I am reading in the dining room"; var reg=/\w+(?=ing)/g; str.match(reg); // ["read", "din"]
說明
\w+
匹配至少一個以上的單詞?=ing
表明右側必須有ing
,可是匹配的結果又不包含ing
g
是全局匹配.css
後綴,但又不能是.min.css
需求說明
這道題曾屢次出如今各大平臺,基本都是依靠零寬斷言來檢測,例如:
test('a.min.css'); // false test('b.css'); // true test('c.mining.css'); // true
代碼
var reg=/^(?!.*\.min\.css$).+\.css$/; reg.test('a.min.css'); // false reg.test('.min.css'); // false reg.test('.css'); // false reg.test('min.css'); // true reg.test('b.css'); // true reg.test('c.mining.css'); // true
說明
因爲只考慮單個文件名的匹配,因此較簡單
?!.*\.min\.css
負向先行斷言試探文件名。這一步匹配完後,直接就排除了xxx.min.css
了(因爲是*
,因此.min.css
也會匹配失敗)\w+.*\.css
匹配xxx.css
這種狀況.css
但不匹配.min.css
RegexBuddy
等工具自行檢測.min.css
文件的文件名需求說明
例如: a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css
(文件以;
隔開)的匹配結果應該是a
與e.a
代碼
var str="a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css"; var reg=/\w+[^;]*(?=\.min\.css)/g; str.match(reg); // ["a", "e.a"]
說明
這類型表達式回溯次數不少,實際中能夠有更好的解決方案,好比先分割,再匹配
\w+[ ^;]*
確保了必須是一個正常的單詞開頭,而且不能包括;
,因此直接排除了名字以.
開頭或名字中包含;
的狀況?=\.min\.css
確保名字右側必須有.min.css
a
與e.a
符合狀況.css
文件的文件名,須要排除.min.css
需求說明
這道題基於上兩題的綜合與變形,增長了點難度(再也不是單個文件名匹配,而是字符串中的文件名提取)
例如: a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css;f.min.a.css
(文件以;
分割)的匹配結果應該是min
、b
、d
和f.min.a
代碼
var str = "a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css;f.min.a.css"; var reg1 = /[^;]+(?=\.css)/g; var match1 = str.match(reg1); var reg2 = /\.min$/; var match2 = []; match1 && match1.map(function(item, index) { !reg2.test(item) && match2.push(item); }); console.log(match1); // ["a.min", ".min", "min", "b", "d", "e.a.min", "f.min.a"] console.log(match2); // ["min", "b", "d", "f.min.a"]
說明
好吧,我認可沒法只靠一個表達式實現這個功能(不知道在座的各位有誰能夠的...)
[ ^;]+(?=\.css)
先匹配全部的.css
後綴的名字\.min$
剔除以.min
結尾的名字.css
但非.min.css
)也行PS:原本準備一步解決做爲壓軸的,可是嘗試了好久都未果,最終仍是拆分來實現的,之因此仍然放在最後,也算是給本身一個警醒
深刻研究後,才發現精通正則表達式真的很難,不少時候,你認爲的已經精通了
只是一種假象。
所以,仍是放下身段,努力學習吧!
初次發佈2017.07.26
於我的博客
http://www.dailichun.com/2017/07/26/regularExpressionZeroWidthAssertion.html