讀jQuery源碼,其中不可避免的要弄明白正則表達式,在此對非捕獲組(non-capturing)進行了一些梳理。
關於捕獲的一些主要用法
- (?:X)
- (?=X)
- (?<=X)
- (?!X)
- (?<!X)
捕獲
要書明白捕獲,就要先從分組開始。重復單字符我們可以使用限定符,如果重復字符串,用什么呢? 對!用小括號,小括號里包裹指定字表達式(子串),這就是分組。之后就可以限定這個子表示的重復次數了。
那么,什么是捕獲呢?使用小括號指定一個子表達式后,匹配這個子表達式的文本(即匹配的內容)可以在表達式或者其他過程中接着用,怎么用呢?至少應該有個指針啥的引用它吧? 對!默認情況下,每個分組(小括號)會自動
擁有一個組號,從左到右,以分組的左括號為標志,第一個出現的分組組號為1,后續遞增。如果出現嵌套,
例如:
“aabcd”
采用正則 (a(b))(c)
match 結果入下:
分組 | 捕獲 |
---|---|
$1(group1) | ab |
$2(group2) | b |
$3(group3) | c |
繼續漲姿勢。
一、(?:)非捕獲組走起。
由下面一個例子引發對非捕獲組的學習。
有兩個金額:6000¥ 和 1000$。
需求是得到金額和貨幣種類。
『廢話少說,多用正則』:
(\d+)+([$¥])
輸出結果為:
OK,滿足了要求。這里的正則分成了兩個組,一個是(d+),一個是(¥$),前一個組($1)匹配金額,后一個組($2)匹配貨幣種類。
現在,需求變了!! 我需要這個正則同時可以匹配浮點數小數點前面的整數。如10010.86¥,提煉出 10010 和 ¥。
那么正則如下:
(\d+)(\.?)(\d)([$¥])
這里用括號分了四組,所以要輸出金額的整數部分和種類,要分別輸了$1,$4了。如果輸出部分和正則是分開的,我希望只修改正則而不去修改輸出部分的代碼,也就是還是用$1,$2作為輸出。由此可以引出非捕獲組(?:)。
把前面的正則修改為:
(\d+)(?:\.?)(?:\d+)([¥$])$
這樣,還是用$1,$2做為輸出,同樣輸出了 10010 和 ¥
這個正則的中間兩個組用到的就是非捕獲組(?:),它可以理解為只分組而不捕獲。
二、(?=)和(?<=) 前后查找
有的資料把它們叫做肯定式向前查找和肯定式向后查找;
有的資料也叫做肯定順序環視和肯定逆序環視。
1、直接看下面的例子:
[0-9a-z]{2}(?=aa) var str = "12332aa438aaf";
Match List:
1 | 32 |
2 | 38 |
這個正則的意思是:匹配這么一個字符串,它要滿足:是兩位字符(數字,或字母),且后面緊跟着兩個a。
分析一下:
32aa 這個子串滿足這個條件,所以可以匹配到,又因為 (?=) 的部分是不捕獲的,所以輸出的只是 32,不包括aa。同理 38aa 也匹配這個正則,而輸出僅是 38。
再深入看一下:
當str第一次匹配成功輸出 32 后,程序要繼續向后查找是否還有匹配的其它子串。那么這時應該從 32aa 的后一位開始向后查找,還是從 32 的后一位呢?也就是從索引 5 開始還是從 7 開始呢?有人可能想到是從 32aa 的下一位開始往后找,因為 32aa 匹配了正則,所以下一位當然是它的后面也就是從 4 開始。但實際上是從 32 的后一位也就是第一個 a 開始往后找。原因還是 (?=) 是非捕獲的。
查閱API文檔是這么注釋的:
(?=X) X, via zero-width positive lookahead
可見zero-width(零寬度)說的就是這個意思。
現在,把字符串寫的更有意思些:str = "aaaaaaaa";
看一下它的輸出: aa aa aa
分析一下:
這個字符串一共有8個a。
第一次匹配比較容易找到,那就是前四個:aaaa ,當然第三和第四個 a 是不捕獲的,所以輸出是第一和第二個a;
接着繼續查找,這時是從第三個a開始,三到六,這4個a區配到了,所以輸出第三和第四個a;
接着繼續查找,這時是從第五個a開始,五到八,這4個a區配到了,所以輸出第五和第六個a;
接着往后查找,這時是從第七個a開始,顯然,第七和第八個a,不滿足正則的匹配條件,查找結束。
我們再延伸一下,剛說的情況的是(?=)放在捕獲的字符串后面,它如果放在前面又是什么結果呢?
例子換成:
(?=hopeful)hope
它的輸出是hope。
正則的意思是:是否能匹配hopeful,如果能,則捕獲hopeful中的hope。當然繼續向后查找匹配的子串,是從f開始。
比較一下可以看出,(?=hopeful)hope 和 hope(?=ful),兩個正則的效果其實是一樣的。
2、下面說一下 (?<=)
把正則改一下,
(?<=aa)[0-9a-z]{2};
字符串還是str = "12332aa438aaf";
它的輸出:43。
這個正則的意思是:匹配這么一個字符串,它要滿足:是兩位字符(數字或字母),且前面緊跟的是兩個字母 a。
同樣,深入一下,把str換成str = "aaaaaaaa";看一下輸出是什么,同樣也是:aa aa aa
分析一下:
第一次匹配不用說,是前四個a,輸出的是第三和第四個a;
繼續向后查找,從第五個a開始,程序發現,第五個和第六個a滿足,因為是兩位字符,且滿足前面緊跟着兩個a(第三和第四個a)。所以匹配成功,輸出第五個和第六個a;
繼續向后查找,從第七個a開始,程序發現,第七個和第八個a滿足,因為是兩位字符,且滿足前面緊跟着兩個a(第五和第六個a)。所以匹配成功,輸出第七和第八個a。查找結束。
三、(?!)和(?<!) 逆襲!
從外觀上看,和前面一組很相似,區別就是把 ‘=’ 換成了 ‘!’
那么意義剛好也是相反的。
[0-9a-z]{2}(?!aa)
意思是:匹配兩個字符,且后面緊跟着的不是aa
(?<!aa)[0-9a-z]{2}
意思是:匹配兩個字符,且前面緊跟着的不是aa
用法和前面講的差不多,這里不再詳述。