正則極簡教程,再也不會學(xué)了忘

點(diǎn)小藍(lán)字加關(guān)注!
周二,公司有技術(shù)講座,一位伙伴分享了爬蟲相關(guān)技巧,其中使用了正則。
在他分享完畢后,有幸,我順道也分享一些正則相關(guān)內(nèi)容。由于當(dāng)時(shí)前后端伙伴都在場,故只講了正則基本語法,并沒有涉及宿主環(huán)境的 API。
本文是這次分享內(nèi)容的總結(jié)。
正則是字符串匹配模式,在處理文本時(shí)很有用。最常見的操作就是用于查找和替換。
說到處理文本,其實(shí)我們每天敲的代碼就是文本,因此常用的代碼編輯器的查找替換工具基本都支持正則語法的。

先說明一下,接下來的內(nèi)容都以《We will rock you》的歌詞測試文本。
使用的正則測試工具是?Regex 101[1]。
這里建議讀者看的過程中,同時(shí)打開該網(wǎng)站,把歌詞貼進(jìn)去,每個(gè)案例都驗(yàn)證一遍。也建議稍微改動一下正則,看看匹配結(jié)果仍是否與自己的理解一致。跟著動手,學(xué)習(xí)效果要好一些。
1. 精確匹配
正則是用來描述字符串的一種模式(pattern),或者說規(guī)律。最平凡的用法,就是精確查找。比如我要找到歌詞中的所有“the”。正則寫成 the 即可。

上圖只找到了一個(gè) the,而不是所有的。這是因?yàn)檎齽t本身是分兩部分的,一部分是模式,另一部分是修飾符(flags,或者叫標(biāo)志位)。一個(gè)常用的修飾符是 g,它單詞 global 的簡寫,表示全局查找。

此時(shí),我們找到了所有“the”。接著我們再找所有“we”。

然而,同時(shí)我們也希望找到文本中“We”,w 字符是大寫的。此時(shí)可以用另外常見的標(biāo)識符 i,單詞 ignoreCase 或者 insensitive 的首字母,表示忽略大小寫。

無論 the 或 we,這種模式匹配都是精確匹配,如果正則只是輸入什么就查找什么,那么其存在的意義就沒有那么大。而它的強(qiáng)大之處在于能實(shí)現(xiàn)模糊匹配。
2. 橫向模糊匹配
比如我們想找到歌詞中所有連續(xù)出現(xiàn)的“e”。

圖中正則形如 p{m,n},表示 p 至少連續(xù)出現(xiàn) m 到 n 次(包括m、n)。p 可以是一個(gè)子模式,不一定只是一個(gè)字符。

上圖中,為了測試我修改了部分歌詞。其中正則使用了括號,括號如你所料一樣,起到了高優(yōu)先級的作用。表示 noise 這個(gè)整體重復(fù)出現(xiàn)了 1 到 3 次。
不知道此時(shí)你是否有疑問,{1,3} 表示 1 到 3 次。為啥上面的匹配結(jié)果只有一個(gè)呢?而不是匹配到 3 個(gè) noise。又或者 noisenoise 和 noise,這兩個(gè)結(jié)果呢?
這是因?yàn)榱吭~有貪婪和惰性之分。{1,3} 這個(gè)量詞是貪婪的,能滿足條件的話,它會盡可能多地匹配。可以在量詞的后面加個(gè)問號,讓其變?yōu)槎栊缘摹?/p>

確實(shí)夠懶得的,找到一個(gè)就滿足了。量詞后面的這個(gè)問號,彷佛是在問量詞,“可以別再貪了嗎?”
量詞的含義清楚了,下來我們來看一些簡寫形式。
?* 等價(jià)于{0,}。即任意多個(gè)。?+ 等價(jià)于{1,}。即至少一個(gè)?? 等價(jià)于{0,1}。即有一個(gè)或者沒有?{m} 等價(jià)于{m,m}
這里要說明的是 ? 這時(shí)就可能兩個(gè)含義。即一個(gè)表示惰性模式,一個(gè)表示量詞。
其實(shí)二者很好區(qū)分,在量詞之后的 ? 才表示惰性匹配。比如正則 bo??y,第一個(gè)問號表示量詞 {0,1},第二個(gè)表示量詞是惰性的。
量詞的存在,能讓正則可以模糊匹配,即很少的模式代碼就能匹配一長串。我稱之為橫向模糊匹配。還有一種縱向的模糊匹配。
3. 縱向模糊匹配
假設(shè)歌詞中有幾處不小心把“rock”寫成“ruck”。我們需要找到二者,可以使用字符集 r[ou]ck。效果如下:

其中 [ou],這種方括號括起來的模式就是字符集。它是一個(gè)集合,匹配“o”或者“u”。又比如我們要找到所有 a 到 e 的字符,可以寫成 [abcde]。這種連續(xù)的字符也可以簡寫成 [a-e]。

字符集是集合的意思,而集合有補(bǔ)集。正則里在方括號內(nèi)開頭加上脫字符,來表示取反[^a-e],匹配一個(gè)不是 a、b、c、d、e 的某字符。

字符類的含義搞清楚了,下來我們來看一下常見的簡寫形式
?\d 等價(jià)于 [0-9]。表示是一位數(shù)字。digit 的首字母。?\D 等價(jià)于 [^0-9]。?\w 等價(jià)于 [0-9a-zA-Z_]。表示數(shù)字、大小寫字母和下劃線。word的首字母,也稱單詞字符。?\W 等價(jià)于 [^0-9a-zA-Z_]。?\s 等價(jià)于 [ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、換行符、回車符、換頁符。記憶方式:s是space character的首字母。?\S 等價(jià)于 [^ \t\v\n\r\f]。?. 等價(jià)于[^\n\r\u2028\u2029]。點(diǎn)是通配符,表示幾乎任意字符。
字符集是正則實(shí)現(xiàn)模糊匹配的另外一種方式,具體到某一位上,要匹配的字符可以是不確定的,我稱之為縱向模糊匹配。
量詞和字符組掌握了話,基本上正則問題能解決一多半。這里再舉一個(gè)例子。找到所有以“ing”結(jié)尾的單詞。

上面使用的是貪婪量詞,如果使用惰性量詞的話,情形會有所不同。

此時(shí),“singing”這個(gè)單詞分成了“sing”和“ing”。要完整的匹配一個(gè)單詞。需要匹配位置。
4. 匹配一個(gè)具體位置
比如匹配“you”這個(gè)單詞,可能會匹配到“your”中的 you。

此時(shí)我們可以使用\b。b 是單詞 boundary 的首字母。它表示匹配一個(gè)位置,這個(gè)位置某一邊是\w,另一邊是\W。也就是一邊是單詞字符,一邊是非單詞字符,因此它叫單詞邊界。
如果對“位置”這一概念,理解得還是不太透徹,我們可以具體看看 \b 到底長什么樣。

注意上圖中得粉色虛線。它們就是一個(gè)個(gè)位置。請看看每一個(gè)是不是兩邊一個(gè)是單詞字符,另一個(gè)是非單詞字符。
位置也是有反義的。比如 \B 表示非單詞邊界。我們也可以看看。

有了單詞字符后,要準(zhǔn)確的匹配單詞“you”,可以使用\byou\b。

除了單詞邊界這種位置之外,估計(jì)大家應(yīng)該知道 ^ 和 $。它匹配整個(gè)文本的開頭和結(jié)尾。

還記得前面我們找“we”嗎,如果我們想找到所有行開頭的 we 單詞。我們可以使用多行模式:

此時(shí)修飾符里多了一個(gè) m,是 multiline 的首字母,表示多行匹配。所謂多行匹配,就是說 ^ 和 $,可以匹配行開頭和行結(jié)尾,不再局限于整個(gè)文本的開頭和結(jié)尾。
除了 \b、\B、^、$ 外,還有一種斷言位置。比如 (?=p),表示模式 p 前面的位置。

(?!p)是其反義。還有反向的斷言,例如 (?<=p),表示模式 p 后面的位置。或者說該位置的后面是 p。它也有反義的形式 (?
關(guān)于位置這一塊兒,多說幾句。假如我想找到這樣的位置,該位置不能是開頭,并且后面的字符是 s,此時(shí)該怎么做呢?

(?!^) 其實(shí)就是 ^ 的反義。連續(xù)寫多個(gè)位置是沒有關(guān)系的。比如寫 ^^^^。

需要注意的位置不同于字符,是不占地方的,如果說是字符也可以,它則是空字符,沒有實(shí)際寬度的。
正則要么匹配字符,要么匹配位置。主體內(nèi)容介紹完了,接下來查缺補(bǔ)漏。
5. 引用
street 里有兩個(gè) e,而 all 里有連個(gè) l。此時(shí)我想找到所有這樣的雙棒字母,該怎么做呢?直接使用 .{2} 是不行的。因?yàn)樗褪?.. 的簡寫形式,表示兩個(gè)任意字符。并沒有要求這兩個(gè)字符相同。

此時(shí),就涉及到了反向引用。參考如下寫法:

\1 是反向引用,表示第一個(gè)括號里捕獲的數(shù)據(jù)。那么 \2 呢,表示第二個(gè)括號捕獲的。

需要注意一點(diǎn)是這里的括號,是平常的括號,而不是像 (?=p) 那樣特殊語法的括號。
括號捕獲的數(shù)據(jù),不僅可以在正則里反向引用。也可以配合宿主 API 來使用,外部引用。比如實(shí)現(xiàn)濾重:

上面使用了替換,工具內(nèi)部必然要用到宿主語言相關(guān) API。$1 表示外部引用第一個(gè)分組捕獲的內(nèi)容。
括號可以用來提供分組功能,又能捕獲數(shù)據(jù)。能否只讓括號充當(dāng)分組功能呢?此時(shí)要使用非捕獲分組(?:p),而不是(p)。
6. 分支結(jié)構(gòu)
比如我想找到所有的 face 和 place。此時(shí)該怎么辦?

管道符 |,表示或的關(guān)系,多選一。它從左到右面一個(gè)個(gè)嘗試,如果成功,就不再繼續(xù)嘗試了。可以說它是短路的、惰性的。比如用 you|your 去匹配 your 時(shí),它只會匹配到 your 的前 3 個(gè)字母。所以分支順序不同結(jié)果可能也會不同。

總結(jié)
本文,通過案例的形式覆蓋了正則常用語法。包括:
?量詞?字符集?分支結(jié)構(gòu)?修飾符?位置?引用?常見簡寫
如果文中每個(gè)例子,你都自己手動輸入調(diào)試并理解的話,你可以放心地說自己正則已經(jīng)入門了。
如果想更全面深入理解JS正則的話,歡迎閱讀本人的?《JS正則迷你書》[2]。
當(dāng)然,只掌握語法是肯定不夠的,還需要大量的練習(xí)。
有一個(gè)燒水理論,如果從來沒把水燒開過,不管燒幾次,水都是不能喝的。可一旦燒開過,哪怕放一陣子也是可以喝的。
初學(xué)又不用,很容易忘,就是這個(gè)道理。
一個(gè)很好練習(xí)的地方是?codewars[3],沒事多刷刷正則相關(guān)題目,用不上幾天就熟悉了。
希望有所幫助。
本文完。
References
[1]?Regex 101:?https://regex101.com/[2]?《JS正則迷你書》:?https://juejin.im/post/5965943ff265da6c30653879[3]?codewars:?https://www.codewars.com/
后記
如果你喜歡探討技術(shù),或者對本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當(dāng)然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。魚頭的微信號是:krisChans95 也可以掃碼關(guān)注公眾號,訂閱更多精彩內(nèi)容。公眾號窗口回復(fù)『?前端資料?』,即可獲取約 200M 前端面試資料,不要錯(cuò)過。
