楔子
為什么要寫(xiě)這篇文章村斟?
最近團(tuán)隊(duì)內(nèi)有小伙伴在閱讀vue的源代碼,然后表示代碼中有很多正則表達(dá)式的應(yīng)用抛猫,而自己卻對(duì)正則不太熟悉蟆盹。后來(lái)我發(fā)現(xiàn)團(tuán)隊(duì)內(nèi)其他的小伙伴也都表示正則表達(dá)式太難了,難學(xué)難用又難看懂闺金。所以逾滥,正則表達(dá)式真的這么難嗎?我的答案是:不是難败匹,是真TM難寨昙。我自己這么些年也是把正則表達(dá)式前前后后學(xué)了起碼五六遍才算是基本能熟練運(yùn)用它讥巡。可是即便如此舔哪,我在準(zhǔn)備這篇文章的過(guò)程中還是學(xué)到了一些新的東西欢顷,可見(jiàn)這玩意兒是有多么的反人類(lèi)了。
既然正則表達(dá)式這么難捉蚤,那我們還要去認(rèn)真學(xué)習(xí)它嗎抬驴?
是的,因?yàn)檎齽t表達(dá)式功能十分強(qiáng)大缆巧,它能極大地提高我們的工作效率和開(kāi)發(fā)效率布持,所以作為一名開(kāi)發(fā)人員,我們必須熟練掌握它盅蝗。
那學(xué)習(xí)正則表達(dá)式有沒(méi)有什么好的辦法或者好的學(xué)習(xí)資料呢鳖链?
當(dāng)然有姆蘸!不然這篇文章寫(xiě)來(lái)干嘛墩莫?話不多說(shuō),我們趕緊開(kāi)始吧逞敷!
本文目標(biāo)
我學(xué)習(xí)正則表達(dá)式的主要資源就是這篇非常有名的《正則表達(dá)式30分鐘入門(mén)教程》狂秦,這篇文章針對(duì)正則表達(dá)式的知識(shí)點(diǎn)寫(xiě)得很詳細(xì)很全面,我接下來(lái)要講的主要知識(shí)點(diǎn)也都來(lái)源于它推捐,不同的是裂问,我會(huì)著重分享一些正則表達(dá)式的記憶技巧來(lái)幫助大家快速掌握這門(mén)技能。本文的目標(biāo)是讓你快速地牛柒、輕松地掌握正則表達(dá)式的基本使用堪簿,以便能快速應(yīng)用到工作中去,所以我推薦你先閱讀我的這篇快速入門(mén)文章皮壁,再去仔細(xì)研讀《正則表達(dá)式30分鐘入門(mén)教程》椭更,以便達(dá)到最佳的學(xué)習(xí)效果。
正文開(kāi)始
初識(shí)正則表達(dá)式
簡(jiǎn)單來(lái)說(shuō)蛾魄,正則表達(dá)式就是一串用于描述文本規(guī)則的代碼虑瀑。舉個(gè)例子,比方說(shuō)你要在一段文本中搜索數(shù)字0滴须,那么你可以直接使用正則表達(dá)式0(你沒(méi)看錯(cuò)舌狗,這就是一個(gè)最簡(jiǎn)單的正則表達(dá)式),如果你想要搜索任意的數(shù)字扔水,這時(shí)你可以使用或符號(hào) | 來(lái)做到這一點(diǎn)痛侍,就像這樣?0|1|2|3|4|5|6|7|8|9,略顯繁瑣魔市?還有一種辦法恋日,就是使用方括號(hào) [] 把你要搜索的字符括起來(lái)膀篮,就像這樣?[0123456789],還是覺(jué)得麻煩岂膳?沒(méi)問(wèn)題誓竿,還有更簡(jiǎn)單的辦法,就是使用?\d?搜索即可(d代表digit谈截,即數(shù)字)筷屡。看到這里簸喂,其實(shí)你已經(jīng)學(xué)習(xí)到了正則表達(dá)式的三個(gè)知識(shí)點(diǎn)了毙死,是不是感覺(jué)也沒(méi)那么難:)。分享一個(gè)我常用的網(wǎng)站:regex101喻鳄,它可以讓你在線測(cè)試正則表達(dá)式的效果(網(wǎng)站的左側(cè)設(shè)置icon中可以設(shè)置中文界面)扼倘,學(xué)習(xí)正則表達(dá)式一定要親自測(cè)試,所以我強(qiáng)烈推薦你動(dòng)手操作一把除呵。
元字符(meta character)
你剛剛已經(jīng)接觸到過(guò)元字符\d了再菊,正則表達(dá)式中還有一些其他的元字符,如下圖所示:
先看元字符\d颜曾,它匹配的是0到9的一個(gè)數(shù)字纠拔。再看元字符\w播赁,這個(gè)厲害了先舷!它不僅能匹配數(shù)字绍在,還能匹配字母和下劃線疙剑,甚至在正則表達(dá)式引擎支持的情況下還能匹配漢字陨献!Wow~Cool~门怪,這里的w意思是word坐梯,也就是一個(gè)單字郎汪,記住word或者Wow~你就記住\w了价卤。接下來(lái)是\s劝萤,s代表space,它可以匹配任意的空白符荠雕,包括空格稳其,制表符(Tab),換行符炸卑,中文全角空格等既鞠,你可以借助死(s)白(b)死(s)白(b)來(lái)記住它,或者你想用sb來(lái)記住它我也不反對(duì)盖文。然后是元字符.嘱蛋,別小看這個(gè)不起眼的點(diǎn),它是最????了,前面的三個(gè)元字符都只是它的子集而已洒敏,它可以匹配除了換行符以外的所有字符龄恋。你可以把這個(gè)點(diǎn)想象成一個(gè)巨大無(wú)比的球,它可以在自己的軌道上碾壓一切凶伙,但就是不可以脫離軌道郭毕,就像下面這張圖一樣:
接下來(lái)是\b和^以及$,它們都只描述一個(gè)位置函荣,并不匹配任何的字符显押。以\b為例,它匹配的是單詞的開(kāi)始或者結(jié)束傻挂,首先需要注意的是乘碑,這里的單詞并不是英文單詞,而是指一個(gè)及以上連續(xù)的\w金拒,所以準(zhǔn)確一點(diǎn)說(shuō)兽肤,\b匹配的是:它的前一個(gè)字符和后一個(gè)字符不全是(一個(gè)是一個(gè)不是或者都不是\w)。舉個(gè)例子绪抛,如果在一篇文章里搜索hi這個(gè)詞的話资铡,你會(huì)搜出來(lái)him、history睦疫、high害驹,如果你用\bhi\b搜的話鞭呕,就會(huì)精準(zhǔn)地搜出來(lái)hi了蛤育。這里的b代表boundary,你也可以使用邊(b)界來(lái)記住它葫松。最后是^和$了瓦糕,它們分別代表字符串的開(kāi)始和結(jié)束,舉個(gè)例子腋么,如果你用abc搜索abcdefabcdef咕娄,你會(huì)搜出來(lái)abcdefabcdef兩個(gè)結(jié)果,但是如果你用^abc搜索的話珊擂,就只會(huì)搜出來(lái)abcdefabcdef一個(gè)結(jié)果圣勒,如果你用^abc$搜索的話,匹配就會(huì)失敗摧扇,因?yàn)樗荒芡晖暾仄ヅ鋋bc這個(gè)字符串圣贸。你可以用這個(gè)故事來(lái)記住這兩個(gè)元字符:一個(gè)勤勞的草帽編織手工藝人,每天早上(字符串開(kāi)始)出門(mén)賣(mài)草帽(元字符^)扛稽,晚上(字符串結(jié)束)賣(mài)完草帽掙了很多錢(qián)($)回家吁峻。
限定符(重復(fù))
所謂限定符就是用于把前面的表達(dá)式重復(fù)N次。比方說(shuō)我們想匹配11位的手機(jī)號(hào)碼,我們可以這樣寫(xiě)\d{11}用含,估計(jì)你也能猜到矮慕,這里的{11}就代表前面的數(shù)字\d重復(fù)11次。我們?cè)谄ヅ銾RL協(xié)議時(shí)可以這么寫(xiě)^https?啄骇,這里的問(wèn)號(hào)?代表前面的s可有可無(wú)痴鳄,所以它可以匹配http和https。我們還有很多種限定符可以用缸夹,具體請(qǐng)參考以下表格:
貪婪與懶惰
正則表達(dá)式默認(rèn)采取貪婪匹配模式夏跷,什么是貪婪匹配模式呢?就是在使得整個(gè)表達(dá)式得以匹配的前提下明未,匹配盡可能多的字符槽华。舉個(gè)例子,如果用a.*b來(lái)匹配aabab的話趟妥,它會(huì)匹配整個(gè)aabab字符串猫态,而如果在*后面添加一個(gè)問(wèn)號(hào)?使其變成a.*?b再去匹配的話,就會(huì)啟用懶惰匹配模式披摄,此時(shí)就會(huì)匹配到aabab和aabab了亲雪。上面講的限定符都可以在后面添加一個(gè)?號(hào)使其變成懶惰匹配模式,如下圖所示:
字符轉(zhuǎn)義
有時(shí)候我們就是想搜索元字符本身的話該怎么辦呢疚膊?這個(gè)時(shí)候我們就得用到反斜線\來(lái)取消這些元字符的特殊含義了义辕,例如想搜索www.beibei.com的話就得使用www\.beibei\.com這樣的表達(dá)式,具體規(guī)則如下圖所示:
字符類(lèi)
字符類(lèi)剛剛其實(shí)我們已經(jīng)接觸過(guò)了寓盗,就是用方括號(hào) [] 把目標(biāo)可選字符括起來(lái)就OK了灌砖,例如[0123456789]就相當(dāng)于\d,我們還可以指定一個(gè)范圍傀蚌,比方說(shuō)[0-9]就相當(dāng)于\d基显,英文字母就是[a-zA-z],而在不考慮中文的情況下善炫,\w就相當(dāng)于[0-9a-zA-Z_]撩幽。
需要特別說(shuō)明的是,很多元字符在方括號(hào)內(nèi)可以不用轉(zhuǎn)義直接使用箩艺,例如[.*+?^$()]可以直接匹配到.*+?^$()中的任意一個(gè)字符窜醉,如下圖所示:
反義
有的時(shí)候,我們想搜索不在某個(gè)范圍內(nèi)的字符時(shí)該怎么辦呢艺谆?方法很簡(jiǎn)單榨惰,對(duì)于\w、\s擂涛、\d和\b的反義只需要把字母大寫(xiě)就可以了读串,例如:\D匹配任意的非數(shù)字聊记,\S匹配任意的非空白符。對(duì)于字符類(lèi)的反義只需要在方括號(hào)內(nèi)用^開(kāi)頭就OK了恢暖,這時(shí)的^指的就是不在該字符類(lèi)范圍內(nèi)的意思排监,而不是字符串的開(kāi)始,例如[^a-z]匹配的就是非小寫(xiě)英文字母杰捂。另外舆床,如果^在方括號(hào)內(nèi)的非開(kāi)頭位置,那么它就代表一個(gè)普通的^字符嫁佳,且不需要轉(zhuǎn)義(還記得上一小節(jié)剛剛提到的嗎挨队?),我們來(lái)看看效果:
分組
分組簡(jiǎn)單來(lái)說(shuō)蒿往,就是一個(gè)用小括號(hào) () 括起來(lái)的子表達(dá)式盛垦。這么做的用途很多,比方說(shuō)可以在分組后加上限定符使得整個(gè)分組重復(fù)N次瓤漏。例如腾夯,我們要匹配一個(gè)像192.168.0.100這樣的IP地址的話,我們就可以這么寫(xiě) (\d{1,3}\.){3}\d{1,3}蔬充。
我們來(lái)分析一下(\d{1,3}\.){3}\d{1,3}這個(gè)表達(dá)式蝶俱,第一部分(\d{1,3}\.)是一個(gè)分組,里面是1 ~ 3位的數(shù)字后面跟著一個(gè)點(diǎn)饥漫,然后是{3}把前面的分組重復(fù)了3次榨呆,最后是一個(gè)1 ~ 3位的數(shù)字。匹配效果如下圖所示:
分枝條件
分枝條件就是使用或符號(hào) | 來(lái)指定幾個(gè)表達(dá)式庸队,只要其中任意一個(gè)表達(dá)式匹配成功的話积蜻,整個(gè)表達(dá)式也就匹配成功了。比方說(shuō)你想要找Chris或者Christopher的話皿哨,你就可以使用Chris|Christopher去匹配浅侨。
另外纽谒,使用分枝條件時(shí)順序也是非常重要的证膨。舉個(gè)例子,\d{5}-\d{4}|\d{5}這個(gè)表達(dá)式用于匹配美國(guó)的郵政編碼鼓黔。美國(guó)郵編的規(guī)則是5位數(shù)字央勒,或者用連字號(hào)間隔的9位數(shù)字。
如果你把它改成\d{5}|\d{5}-\d{4}的話澳化,那么就只會(huì)匹配5位的郵編(以及9位郵編的前5位)崔步。原因是匹配分枝條件時(shí),將會(huì)從左到右地測(cè)試每個(gè)條件缎谷,如果滿足了某個(gè)分枝的話井濒,就不會(huì)去再管其它的條件了。
后向引用
從這里開(kāi)始,情況就稍稍變得麻煩一點(diǎn)了瑞你。比方說(shuō)你想要搜索go go或者kitty kitty這樣的有重復(fù)情況的字符串酪惭,那該怎么辦呢?如果只用前面講到的知識(shí)點(diǎn)是無(wú)法做到的者甲,這時(shí)候就需要用到后向引用了春感。
正則表達(dá)式\b(\w+)\b\s+\1\b即可實(shí)現(xiàn)上述需求,我們來(lái)分析一下:首先是\b(\w+)\b代表一個(gè)左右兩邊都是邊界的單詞虏缸,然后是\s+匹配不少于1個(gè)的空白符鲫懒,接下來(lái)的\1也是我們的重點(diǎn),它代表前面的\b(\w+)\b中括號(hào)內(nèi)的\w+匹配到的內(nèi)容刽辙,這樣就能滿足需求了窥岩,我們來(lái)看看它的效果吧:
解釋一下為什么我們可以這么做,正則表達(dá)式引擎在解析正則表達(dá)式時(shí)宰缤,會(huì)從左到右掃描每一個(gè)分組谦秧,以左括號(hào)出現(xiàn)的順序依次為它們分配組號(hào)1、組號(hào)2……依此類(lèi)推撵溃,各個(gè)分組捕獲的內(nèi)容會(huì)被保存下來(lái)疚鲤,然后就可以使用\1、\2……來(lái)引用前面分組捕獲的內(nèi)容了缘挑。補(bǔ)充說(shuō)一句集歇,分組0會(huì)保存整個(gè)表達(dá)式匹配到的字符串。
另外语淘,我們還可以通過(guò)(?:exp)這樣的方式取消該分組對(duì)組號(hào)的分配權(quán)诲宇,這么做有什么應(yīng)用場(chǎng)景呢?當(dāng)然有惶翻!比方說(shuō)姑蓝,我要用JavaScript的正則匹配和提取一個(gè)簡(jiǎn)單URL的協(xié)議和域名部分,其中協(xié)議部分可以省略吕粗,我一開(kāi)始使用的正則是這樣的:((https?):)?(\/\/)?([^?]+)
嘗試后發(fā)現(xiàn)纺荧,匹配的結(jié)果包含0 ~ 4共5個(gè)分組,刨去分組0颅筋,其實(shí)我只需要分組2(https)和分組4(www.beibei.com)宙暇,分組1(https:)和分組3(//)都是我不需要的,那么這個(gè)時(shí)候我就可以取消分組1和分組3對(duì)組號(hào)的分配權(quán)议泵,以便更精準(zhǔn)地拿到我想要的結(jié)果占贫。改過(guò)之后的正則是這樣的:(?:(https?):)?(?:\/\/)?([^?]+),注意我們給分組1和分組3的括號(hào)內(nèi)添加了?:的前綴先口,最終的效果如下:
另外型奥,我們還可以給分組設(shè)置自定義的組名瞳收,具體方法我就不累述了,請(qǐng)自行參閱《正則表達(dá)式30分鐘入門(mén)教程》的后向引用章節(jié)厢汹。
注釋
正則表達(dá)式也可以通過(guò) (?#comment) 這樣的語(yǔ)法來(lái)添加注釋缎讼,不過(guò)我個(gè)人并不建議使用它,因?yàn)樾枰砑幼⑨尩恼齽t就夠復(fù)雜了坑匠,再在里面加注釋就會(huì)讓正則變得愈加難懂血崭,所以還是把注釋加在外面吧。
零寬斷言
正則表達(dá)式的內(nèi)容還有很多厘灼,但是在實(shí)戰(zhàn)中基本上到這里就夠用了夹纫,所以零寬斷言是我們學(xué)習(xí)的最后一個(gè)也是最復(fù)雜的一個(gè)知識(shí)點(diǎn)。
首先设凹,如果你看過(guò)其他文章對(duì)于它們的描述的話舰讹,我敢保證你絕對(duì)會(huì)被那些拗口的、鋼鐵直男翻譯的名詞所打敗闪朱。什么“零寬度正預(yù)測(cè)先行斷言”月匣、“零寬度負(fù)回顧后發(fā)斷言”……WTF?奋姿?
可是當(dāng)你明白了它們的含義锄开,再看看它們的英文單詞之后,你可能會(huì)有一股想打人的沖動(dòng)称诗,這么簡(jiǎn)單的概念為什么被這些人整得這么復(fù)雜萍悴?這簡(jiǎn)直比把Socket翻譯成套接字更坑爹啊~不扯這些沒(méi)用的了,我們直接來(lái)看它們到底是個(gè)啥東東寓免?
簡(jiǎn)單來(lái)說(shuō)癣诱,它們跟\b、^袜香、$一樣撕予,只是描述一個(gè)位置,但并不匹配任何的字符蜈首。例如实抡,我想要查找James這個(gè)單詞,但是要求James前面是Lebron加一個(gè)空格疾就,也就是說(shuō)我的目標(biāo)是Lebron James中的James這個(gè)單詞澜术,如果我直接搜索James的話,很可能搜出來(lái)Harry James猬腰、Chris James這樣我不想要的東西,那么這個(gè)時(shí)候我就可以使用(?<=Lebron )James這樣的正則來(lái)進(jìn)行搜索猜敢,站在(?<=Lebron )的角度姑荷,它描述的是我要找到Lebron 這樣一個(gè)位置盒延,它的后面跟著的是James,所以這個(gè)表達(dá)式就能幫我準(zhǔn)確地找到Lebron?James中的James了鼠冕。
站在表達(dá)式的角度添寺,按照前面或者后面以及是或者不是,我們有以下四種情況:
Positive Lookahead?(?=) —?Find expression A where expression B follows: A(?=B)
Negative Lookahead?(?!)?—?Find expression A where expression B does not follows:?A(?!B)
Positive Lookbehind (?<=)?—?Find expression A where expression B precedes:?(?<=B)A
Negative Lookbehind?(?<!)?—?Find expression A where expression B does not precedes:?(?<!B)A
看它們的英文單詞應(yīng)該就能明白它們的含義了懈费,不過(guò)想要記住它們確實(shí)有些困難计露,所以接下來(lái)我就分享一下我的記憶技巧:
1、首先是一句詢問(wèn)(?)
2憎乙、然后確定位置票罐,你可以把<想象成一個(gè)方向箭頭,表達(dá)式默認(rèn)放在目標(biāo)的后面xxx(?)泞边,加了方向箭頭<就被拉到了目標(biāo)的前面(?<)target
3该押、最后確定是要使用等號(hào)=還是使用感嘆號(hào)!。等號(hào)=代表“是”的話那么感嘆號(hào)“!”就代表不是了阵谚。這樣四種情況我們就能夠很容易地記下來(lái)了:)
再來(lái)看一個(gè)完整的例子:
需要特別說(shuō)明的是蚕礼,JavaScript早些時(shí)候的版本是不支持Positive Lookbehind?(?<=)和Negative?Lookbehind?(?<!)的,也就是帶方向箭頭<的都不支持梢什,但是我發(fā)現(xiàn)Chrome現(xiàn)在也已經(jīng)支持它們了奠蹬,但是因?yàn)橛脩舻倪\(yùn)行環(huán)境各異,所以建議還是謹(jǐn)慎使用吧嗡午。
寫(xiě)在最后
其實(shí)我還有“處理選項(xiàng)”和“平衡組/遞歸匹配”沒(méi)有提到罩润,“處理選項(xiàng)”建議直接去《正則表達(dá)式30分鐘入門(mén)教程》中查看,“平衡組/遞歸匹配”我試了一下發(fā)現(xiàn)沒(méi)有哪個(gè)在線測(cè)試工具是支持的翼馆,所以也就不講了割以。
除了我推薦的regex101可以用來(lái)在線測(cè)試正則表達(dá)式之外,還有兩個(gè)可以以圖形化的方式展示正則表達(dá)式的網(wǎng)站也很不錯(cuò)应媚,它們分別是:https://regexper.com/和https://jex.im/regulex严沥。另外,還有一個(gè)不錯(cuò)的交互式學(xué)習(xí)正則表達(dá)式的網(wǎng)站也值得一試:Try Regex中姜。
由于時(shí)間倉(cāng)促以及作者能力有限消玄,文中若有任何錯(cuò)誤或者未盡之處,懇請(qǐng)留言批評(píng)指正丢胚,感謝您的閱讀~