(推薦)JS正則知識(shí)點(diǎn)專題:https://www.jb51.net/article/139831.htm
正則啊,就像一座燈塔,當(dāng)你在字符串的海洋不知所措的時(shí)候,總能給你一點(diǎn)思路;正則啊,就像一臺(tái)驗(yàn)鈔機(jī),在你不知道用戶提交的鈔票真假的時(shí)候,總能幫你一眼識(shí)別;正則啊,就像一個(gè)手電筒,在你需要找什么玩意的時(shí)候,總能幫你get你要的東西...
—— 節(jié)選自 Stinson 同學(xué)的語(yǔ)文排比句練習(xí)《正則》
欣賞了一段文學(xué)節(jié)選后,我們正式來(lái)梳理一遍JS中的正則,本文的首要目的是,防止我經(jīng)常忘記正則的一些用法,故梳理和寫下來(lái)加強(qiáng)熟練度和用作參考,次要目的是與君共勉,如有紕漏,請(qǐng)不吝賜教,良辰謝過(guò)。
本文既然取題為“一條龍”,就要對(duì)得起”龍”,故將包括正則原理、語(yǔ)法一覽、JS(ES5)中的正則、ES6對(duì)正則的擴(kuò)展、實(shí)踐正則的思路,我盡量深入盡量淺出地去講這些東西(搞得好像真能深入淺出一樣的),如果你只想知道怎么應(yīng)用,那么看第二、三、五部分,基本就能滿足你的需求了,如果想掌握J(rèn)S中的正則的,那么還是委屈你跟著我的思路來(lái)吧,嘿嘿嘿!
一、原理概論
在一開(kāi)始用正則的時(shí)候,就覺(jué)得神奇,計(jì)算機(jī)究竟是怎么根據(jù)一個(gè)正則表達(dá)式來(lái)匹配字符串的?直到后來(lái)我遇到了一本書叫《計(jì)算理論》,看到了正則、DFA、NFA的概念和相互間的聯(lián)系,才有一些恍然小悟的意思。
但如果真的要從原理上吃透正則表達(dá)式,那么恐怕最好的方式是:
1. 首先去找一本專門講正則的書去看看,O'REILLY的“動(dòng)物總動(dòng)員”系列里就有;
2. 再自己實(shí)現(xiàn)一個(gè)正則引擎。
而本文的重點(diǎn)在于JS中正則的應(yīng)用,故原理僅作簡(jiǎn)單介紹(因?yàn)槲乙矝](méi)寫過(guò)正則引擎,也不深入),一來(lái)大致“糊弄下”像我一樣的好奇寶寶們對(duì)正則原理的疑惑,二來(lái)知道一些原理方面基本的知識(shí),對(duì)于理解語(yǔ)法和寫正則是大有裨益的。
1. 正則引擎
為什么正則能有效,因?yàn)橛幸?,這和為什么JS能執(zhí)行一樣,有JS引擎,所謂正則引擎,可以理解為根據(jù)你的正則表達(dá)式用算法去模擬一臺(tái)機(jī)器,這臺(tái)機(jī)器有很多狀態(tài),通過(guò)讀取待測(cè)的字符串,在這些狀態(tài)間跳來(lái)跳去,如果最后停在了“終結(jié)狀態(tài)”(Happy Ending),那么就Say I Do,否則Say You Are a Good Man。如此將一個(gè)正則表達(dá)式轉(zhuǎn)換為一個(gè)可在有限的步數(shù)中計(jì)算出結(jié)果的機(jī)器,那么就實(shí)現(xiàn)了引擎。
正則的引擎大致可分為兩類:DFA和NFA
1. DFA (Deterministic finite automaton) 確定型有窮自動(dòng)機(jī)
2. NFA (Non-deterministic finite automaton) 非確定型有窮自動(dòng)機(jī),大部分都是NFA
這里的“確定型”指,對(duì)于某個(gè)確定字符的輸入,這臺(tái)機(jī)器的狀態(tài)會(huì)確定地從a跳到b,“非確定型”指,對(duì)于某個(gè)確定字符的輸入,這臺(tái)機(jī)器可能有好幾種狀態(tài)的跳法;這里的“有窮”指,狀態(tài)是有限的,可以在有限的步數(shù)內(nèi)確定某個(gè)字符串是被接受還是發(fā)好人卡的;這里的“自動(dòng)機(jī)”,可以理解為,一旦這臺(tái)機(jī)器的規(guī)則設(shè)定完成,就可以自行判斷了,不要人看。
DFA引擎不需要進(jìn)行回溯,所以匹配效率一般情況下要高,但是它并不支持捕獲組,于是也就不支持反向引用和$這種形式的引用,也不支持環(huán)視(Lookaround)、非貪婪模式等一些NFA引擎特有的特性。
如果想更詳細(xì)地了解正則、DFA、NFA,那么可以去看一下《計(jì)算理論》,然后你可以根據(jù)某個(gè)正則表達(dá)式自己畫出一臺(tái)自動(dòng)機(jī)。
2. 知識(shí)儲(chǔ)備
這一小節(jié)對(duì)于你理解正則表達(dá)式很有用,尤其是明白什么是字符,什么是位置。
2.1 正則眼中的字符串——n個(gè)字符,n+1個(gè)位置
在上面的“笑聲”字符串中,一共有8個(gè)字符,這是你能看到的,還有9個(gè)位置,這是聰明的人才能看到的。為什么要有字符還要有位置呢?因?yàn)槲恢檬强梢员黄ヅ涞摹?/p>
那么進(jìn)一步我們?cè)賮?lái)理解“占有字符”和“零寬度”:
- 如果一個(gè)子正則表達(dá)式匹配到的是字符,而不是位置,而且會(huì)被保存到最終的結(jié)果中,那個(gè)這個(gè)子表達(dá)式就是占有字符的,比如/ha/(匹配ha)就是占有字符的;
- 如果一個(gè)子正則匹配的是位置,而不是字符,或者匹配到的內(nèi)容不保存在結(jié)果中(其實(shí)也可以看做一個(gè)位置),那么這個(gè)子表達(dá)式是零寬度的,比如/read(?=ing)/(匹配reading,但是只將read放入結(jié)果中,下文會(huì)詳述語(yǔ)法,此處僅僅舉例用),其中的(?=ing)就是零寬度的,它本質(zhì)代表一個(gè)位置。
占有字符是互斥的,零寬度是非互斥的。也就是一個(gè)字符,同一時(shí)間只能由一個(gè)子表達(dá)式匹配,而一個(gè)位置,卻可以同時(shí)由多個(gè)零寬度的子表達(dá)式匹配。舉個(gè)栗子,比如/aa/是匹配不了a的,這個(gè)字符串中的a只能由正則的第一個(gè)a字符匹配,而不能同時(shí)由第二個(gè)a匹配(廢話);但是位置是可以多個(gè)匹配的,比如/\b\ba/是可以匹配a的,雖然正則表達(dá)式里有2個(gè)表示單詞開(kāi)頭位置的\b元字符,這兩個(gè)\b是可以同時(shí)匹配位置0(在這個(gè)例子中)的。
注意:我們說(shuō)字符和位置是面向字符串說(shuō)的,而說(shuō)占有字符和零寬度是面向正則說(shuō)的。
2.2 控制權(quán)和傳動(dòng)
這兩個(gè)詞可能在搜一些博文或者資料的時(shí)候會(huì)遇到,這里做一個(gè)解釋先:
控制權(quán)是指哪一個(gè)正則子表達(dá)式(可能為一個(gè)普通字符、元字符或元字符序列組成)在匹配字符串,那么控制權(quán)就在哪。
傳動(dòng)是指正則引擎的一種機(jī)制,傳動(dòng)裝置將定位正則從字符串的哪里開(kāi)始匹配。
正則表達(dá)式當(dāng)開(kāi)始匹配的時(shí)候,一般是由一個(gè)子表達(dá)式獲取控制權(quán),從字符串中的某一個(gè)位置開(kāi)始嘗試匹配,一個(gè)子表達(dá)式開(kāi)始嘗試匹配的位置,是從前一子表達(dá)匹配成功的結(jié)束位置開(kāi)始的。
舉一個(gè)栗子,read(?=ing)ing\sbook匹配reading book,我們把這個(gè)正則看成5個(gè)子表達(dá)式read、(?=ing)、ing、\s、book,當(dāng)然你也可以吧read看做4個(gè)單獨(dú)字符的子表達(dá)式,只是我們這里為了方便這么看待。read從位置0開(kāi)始匹配到位置4,后面的(?=ing)繼續(xù)從位置4開(kāi)始匹配,發(fā)現(xiàn)位置4后面確實(shí)是ing,于是斷言匹配成功,也就是整一個(gè)(?=ing)就是匹配了位置4這一個(gè)位置而已(這里更能理解什么是零寬了吧),然后后面的ing再?gòu)奈恢?開(kāi)始匹配到位置7,然后\s再?gòu)奈恢?匹配到位置8,最后的book從位置8匹配到位置12,整一個(gè)匹配完成。
3. 匹配之旅“淺”度游(可跳過(guò))
說(shuō)了那么多,我們把自己當(dāng)做一個(gè)正則引擎,一步一步以最小的單位——“字符”和“位置”——去看一下正則匹配的過(guò)程,舉幾個(gè)栗子。
3.1 基本匹配
正則表達(dá)式:easy
源字符串:So easy
匹配過(guò)程:首先由正則表達(dá)式字符e取得控制權(quán),從字符串的位置0開(kāi)始匹配,遇到字符串字符‘S',匹配失敗,然后正則引擎向前傳動(dòng),從位置1開(kāi)始嘗試,遇到字符串字符‘o',匹配失敗,繼續(xù)傳動(dòng),后面的空格自然也失敗,于是從位置3開(kāi)始嘗試匹配,成功匹配字符串字符‘e',控制權(quán)交給正則表達(dá)式子表達(dá)式(這里也是一個(gè)字符)a,嘗試從上次匹配成功的結(jié)束位置4開(kāi)始匹配,成功匹配字符串字符‘a(chǎn)',后面一直如此匹配到‘y',然后匹配完成,匹配結(jié)果為easy。
3.2 零寬匹配
正則:^(?=[aeiou])[a-z]+$
源字符串:apple
首先這個(gè)正則表示:匹配這樣一個(gè)從頭到尾完整的字符串,這整一個(gè)字符串僅由小寫字母組成,并且以a、e、i、o、u這5個(gè)字母任一字母開(kāi)頭。
匹配過(guò)程:首先正則的^(表示字符串開(kāi)始的位置)獲取控制權(quán),從位置0開(kāi)始匹配,匹配成功,控制權(quán)交給(?=[aeiou]),這個(gè)子表達(dá)式要求該位置右邊必須是元音小寫字母中的一個(gè),零寬子表達(dá)式相互間不互斥,所以從位置0開(kāi)始嘗試匹配,右側(cè)是字符串的‘a(chǎn)',符合因此匹配成功,所以(?=[aeiou])匹配此處的位置0匹配成功,控制權(quán)交給[a-z]+,從位置0開(kāi)始匹配,字符串‘a(chǎn)pple'中的每個(gè)字符都匹配成功,匹配到字符串末尾,控制權(quán)交回正則的$,嘗試匹配字符串結(jié)束位置,成功,至此,整個(gè)匹配完成。
3.3 貪婪匹配和非貪婪匹配
正則1:{.*}
正則2:{.*?}
源字符串:{233}
這里有兩個(gè)正則,在限定符(語(yǔ)法會(huì)講什么是限定符)后面加?符號(hào)表示忽略優(yōu)先量詞,也就是非貪婪匹配,這個(gè)栗子我剝得快一點(diǎn)。
首先開(kāi)頭的{匹配,兩個(gè)正則都是一樣的表現(xiàn)。
正則1的.*為貪婪匹配,所以一直匹配余下字符串'233}',匹配到字符串結(jié)束位置,只是每次匹配,都記錄一個(gè)備選狀態(tài),為了以后回溯,每次匹配有兩條路,選擇了匹配這條路,但記一下這里還可以有不匹配這條路,如果前面死胡同了,可以退回來(lái),此時(shí)控制權(quán)交還給正則的},去匹配字符串結(jié)束位置,失敗,于是回溯,意思就是說(shuō)前面的.*你吃的太多了,吐一個(gè)出來(lái),于是控制權(quán)回給.*,吐出一個(gè)}(其實(shí)是用了前面記錄的備選狀態(tài),嘗試不用.*去匹配'}'),控制權(quán)再給正則的},這次匹配就成功了。
正則2的.*?為非貪婪匹配,盡可能少地匹配,所以匹配'233}'的每一個(gè)字符的時(shí)候,都是嘗試不匹配,但是一但控制權(quán)交還給最后的}就發(fā)現(xiàn)出問(wèn)題了,趕緊回溯乖乖匹配,于是每一個(gè)字符都如此,最終匹配成功。
云里霧里?這就對(duì)了!可以移步去下面推薦的博客看看:
想詳細(xì)了解貪婪和非貪婪匹配原理以及獲取更多正則相關(guān)原理,除了看書之外,推薦去一個(gè)CSDN的博客 雁過(guò)無(wú)痕-博客頻道 - CSDN.NET ,講解得很詳細(xì)和透徹
二、語(yǔ)法一覽
正則的語(yǔ)法相信許多人已經(jīng)看過(guò)deerchao寫的30分鐘入門教程,我也是從那篇文字中入門的,deerchao從語(yǔ)法邏輯的角度以.NET正則的標(biāo)準(zhǔn)來(lái)講述了正則語(yǔ)法,而我想重新組織一遍,以便于應(yīng)用的角度、以JS為宿主語(yǔ)言來(lái)重新梳理一遍語(yǔ)法,這將便于我們把語(yǔ)言描述翻譯成正則表達(dá)式。
下面這張一覽圖(可能需要放大),整理了常用的正則語(yǔ)法,并且將JS不支持的語(yǔ)法特性以紅色標(biāo)注出來(lái)了(正文將不會(huì)描述這些不支持的特性),語(yǔ)法部分的詳細(xì)描述也將根據(jù)下面的圖,從上到下,從左到右的順序來(lái)梳理,盡量不啰嗦。
1. 要用某類常見(jiàn)字符——簡(jiǎn)單元字符
為什么這里要加簡(jiǎn)單2個(gè)字,因?yàn)樵谡齽t中,\d、\w這樣的叫元字符,而{n,m}、(?!exp)這樣的也叫元字符,所以元字符是在正則中有特定意義的標(biāo)識(shí),而這一小節(jié)講的是簡(jiǎn)單的一些元字符。
- .匹配除了換行符以外的任意字符,也即是[^\n],如果要包含任意字符,可使用(.|\n)
- \w匹配任意字母、數(shù)字或者下劃線,等價(jià)于[a-zA-Z0-9_],在deerchao的文中還指出可匹配漢字,但是\w在JS中是不能匹配漢字的
- \s匹配任意空白符,包含換頁(yè)符\f、換行符\n、回車符\r、水平制表符\t、垂直制表符\v
- \d匹配數(shù)字
- \un匹配n,這里的n是一個(gè)有4個(gè)十六進(jìn)制數(shù)字表示的Unicode字符,比如\u597d表示中文字符“好”,那么超過(guò)\uffff編號(hào)的字符怎么表示呢?ES6的u修飾符會(huì)幫你。
2. 要表示出現(xiàn)次數(shù)(重復(fù))——限定符
- a*表示字符a連續(xù)出現(xiàn)次數(shù) >= 0 次
- a+表示字符a連續(xù)出現(xiàn)次數(shù) >= 1 次
- a?表示字符a出現(xiàn)次數(shù) 0 或 1 次
- a{5}表示字符a連續(xù)出現(xiàn)次數(shù) 5 次
- a{5,}表示字符a連續(xù)出現(xiàn)次數(shù) >= 5次
- a{5,10}表示字符a連續(xù)出現(xiàn)次數(shù)為 5到10次 ,包括5和10
3. 匹配位置——定位符和零寬斷言
匹配某個(gè)位置的表達(dá)式都是零寬的,這是主要包含兩部分,一是定位符,匹配一個(gè)特定位置,二是零寬斷言,匹配一個(gè)要滿足某要求的位置。
定位符有以下幾個(gè)常用的:
- \b匹配單詞邊界位置,準(zhǔn)確的描述是它匹配一個(gè)位置,這個(gè)位置前后不全是\w能描述的字符,所以像\u597d\babc是可以匹配“好abc”的。
- ^匹配字符串開(kāi)始位置,也就是位置0,如果設(shè)置了 RegExp 對(duì)象的 Multiline 屬性,^ 也匹配 '\n' 或 '\r' 之后的位置
- $匹配字符串結(jié)束位置,如果設(shè)置了RegExp 對(duì)象的 Multiline 屬性,$ 也匹配 '\n' 或 '\r' 之前的位置
零寬斷言(JS支持的)有以下兩個(gè):
- (?=exp)匹配一個(gè)位置,這個(gè)位置的右邊能匹配表達(dá)式exp,注意這個(gè)表達(dá)式僅僅匹配一個(gè)位置,只是它對(duì)于這個(gè)位置的右邊有要求,而右邊的東西是不會(huì)被放進(jìn)結(jié)果的,比如用read(?=ing)去匹配“reading”,結(jié)果是“read”,而“ing”是不會(huì)放進(jìn)結(jié)果的
- (?!exp)匹配一個(gè)位置,這個(gè)位置的右邊不能匹配表達(dá)式exp
4. 想表達(dá)“或”的意思——字符簇和分歧
我們經(jīng)常會(huì)表達(dá)“或”的含義,比如這幾個(gè)字符中的任意一個(gè)都行,再比如匹配5個(gè)數(shù)字或者5個(gè)字母都行等等需求。
字符簇可用來(lái)表達(dá)字符級(jí)別的“或”語(yǔ)義,表示的是方括號(hào)中的字符任選一:
- [abc]表示a、b、c這3個(gè)字符中的任意一個(gè),如果字母或者數(shù)字是連續(xù)的,那么可以用-連起來(lái)表示,[b-f]代表從b到f這么多字符中任選一個(gè)
- [(ab)(cd)]并不會(huì)用來(lái)匹配字符串“ab”或“cd”,而是匹配a、b、c、d、(、)這6個(gè)字符中的任一個(gè),也就是想表達(dá)“匹配字符串a(chǎn)b或者cd”這樣的需求不能這么做,要這么寫ab|cd。但這里要匹配圓括號(hào)本身,講道理是要反斜杠轉(zhuǎn)義的,但是在方括號(hào)中,圓括號(hào)被當(dāng)成普通字符看待,即便如此,仍然建議顯式地轉(zhuǎn)義
分歧用來(lái)表達(dá)表達(dá)式級(jí)別的“或”語(yǔ)義,表示的是匹配|左右任一表達(dá)就可:
- ab|cd會(huì)匹配字符串“ab”或者“cd”
- 會(huì)短路,回想下編程語(yǔ)言中邏輯或的短路,所以用(ab|abc)去匹配字符串“abc”,結(jié)果會(huì)是“ab”,因?yàn)樨Q線左邊的已經(jīng)滿足了,就用左邊的匹配結(jié)果代表整個(gè)正則的結(jié)果
5. 想表達(dá)“非”的意思——反義
有時(shí)候我們想表達(dá)“除了某些字符之外”這樣的需求,這個(gè)時(shí)候就要用到反義
- \W、\D、\S、\B 用大寫字母的這幾個(gè)元字符表示就是對(duì)應(yīng)小寫字母匹配內(nèi)容的反義,這幾個(gè)依次匹配“除了字母、數(shù)字、下劃線外的字符”、“非數(shù)字字符”、“非空白符”、“非單詞邊界位置”
- [^aeiou]表示除了a、e、i、o、u外的任一字符,在方括號(hào)中且出現(xiàn)在開(kāi)頭位置的^表示排除,如果^在方括號(hào)中不出現(xiàn)在開(kāi)頭位置,那么它僅僅代表^字符本身
6. 整體看待和捕獲——分組和后向引用
其實(shí)你在上面的一些地方已經(jīng)看到了圓括號(hào),是的,圓括號(hào)就是用來(lái)分組的,括在一對(duì)括號(hào)里的就是一個(gè)分組。
上面講的大部分是針對(duì)字符級(jí)別的,比如重復(fù)字母 “A” 5次,可以用A{5}來(lái)表示,但是如果想要字符串“ABC”重復(fù)5次呢?這個(gè)時(shí)候就需要用到括號(hào)。
括號(hào)的第一個(gè)作用,將括起來(lái)的分組當(dāng)做一個(gè)整體看待,所以你可以像對(duì)待字符重復(fù)一樣在一個(gè)分組后面加限定符,比如(ABC){5}。
分組匹配到的內(nèi)容也就是這個(gè)分組捕獲到的內(nèi)容,從左往右,以左括號(hào)為標(biāo)志,每個(gè)分組會(huì)自動(dòng)擁有一個(gè)從1開(kāi)始的編號(hào),編號(hào)0的分組對(duì)應(yīng)整個(gè)正則表達(dá)式,JS不支持捕獲組顯示命名。
括號(hào)的第二個(gè)作用,分組捕獲到的內(nèi)容,可以在之后通過(guò)\分組編號(hào)的形式進(jìn)行后向引用。比如(ab|cd)123\1可以匹配“ab123ab”或者“cd123cd”,但是不能匹配“ab123cd”或“cd123ab”,這里有一對(duì)括號(hào),也是第一對(duì)括號(hào),所以編號(hào)為捕獲組1,然后在正則中通過(guò)\1去引用了捕獲組1的捕獲的內(nèi)容,這叫后向引用。
括號(hào)的第三個(gè)作用,改變優(yōu)先級(jí),比如abc|de和(abc|d)e表達(dá)的完全不是一個(gè)意思。
7. 轉(zhuǎn)義
任何在正則表達(dá)式中有作用的字符都建議轉(zhuǎn)義,哪怕有些情況下不轉(zhuǎn)義也能正確,比如[]中的圓括號(hào)、^符號(hào)等。
8. 優(yōu)先級(jí)問(wèn)題
優(yōu)先級(jí)從高到低是:
- 轉(zhuǎn)義 \
- 括號(hào)(圓括號(hào)和方括號(hào))(), (?:), (?=), []
- 字符和位置
- 豎線 |
9. 貪婪和非貪婪
在限定符中,除了{(lán)n}確切表示重復(fù)幾次,其余的都是一個(gè)有下限的范圍。
在默認(rèn)的模式(貪婪)下,會(huì)盡可能多的匹配內(nèi)容。比如用ab*去匹配字符串“abbb”,結(jié)果是“abbb”。
而通過(guò)在限定符后面加問(wèn)號(hào)?可以進(jìn)行非貪婪匹配,會(huì)盡可能少地匹配。用ab*?去匹配“abbb”,結(jié)果會(huì)是“a”。
不帶問(wèn)號(hào)的限定符也稱匹配優(yōu)先量詞,帶問(wèn)號(hào)的限定符也稱忽略匹配優(yōu)先量詞。
10. 修飾符(匹配選項(xiàng))
其實(shí)正則的匹配選項(xiàng)有很多可選,不同的宿主語(yǔ)言環(huán)境下可能各有不同,此處就JS的修飾符作一個(gè)說(shuō)明:
- 加g修飾符:表示全局匹配,模式將被應(yīng)用到所有字符串,而不是在發(fā)現(xiàn)第一個(gè)匹配項(xiàng)時(shí)停止
- 加i修飾符:表示不區(qū)分大小寫
- 加m修飾符:表示多行模式,會(huì)改變^和$的行為,上文已述
三、JS(ES5)中的正則
JS中的正則由引用類型RegExp表示,下面主要就RegExp類型的創(chuàng)建、兩個(gè)主要方法和構(gòu)造函數(shù)屬性來(lái)展開(kāi),然后會(huì)提及String類型上的模式匹配,最后會(huì)簡(jiǎn)單羅列JS中正則的一些局限。
1. 創(chuàng)建正則表達(dá)式
一種是用字面量的方式創(chuàng)建,一種是用構(gòu)造函數(shù)創(chuàng)建,我們始終建議用前者。
//創(chuàng)建一個(gè)正則表達(dá)式
var exp = /pattern/flags;
//比如
var pattern=/\b[aeiou][a-z]+\b/gi;
//等價(jià)下面的構(gòu)造函數(shù)創(chuàng)建
var pattern=new RegExp("\\b[aeiou][a-z]+\\b","gi");
其中pattern可以是任意的正則表達(dá)式,flags部分是修飾符,在上文中已經(jīng)闡述過(guò)了,有 g、i、m 這3個(gè)(ES5中)。
現(xiàn)在說(shuō)一下為什么不要用構(gòu)造函數(shù),因?yàn)橛脴?gòu)造函數(shù)創(chuàng)建正則,可能會(huì)導(dǎo)致對(duì)一些字符的雙重轉(zhuǎn)義,在上面的例子中,構(gòu)造函數(shù)中第一個(gè)參數(shù)必須傳入字符串(ES6可以傳字面量),所以字符\ 會(huì)被轉(zhuǎn)義成\,因此字面量的\b會(huì)變成字符串中的\\b,這樣很容易出錯(cuò),賊多的反斜杠。
2. RegExp上用來(lái)匹配提取的方法——exec()
var matches=pattern.exec(str);
接受一個(gè)參數(shù):源字符串
返回:結(jié)果數(shù)組,在沒(méi)有匹配項(xiàng)的情況下返回null
結(jié)果數(shù)組包含兩個(gè)額外屬性,index表示匹配項(xiàng)在字符串中的位置,input表示源字符串,結(jié)果數(shù)組matches第一項(xiàng)即matches[0]表示匹配整個(gè)正則表達(dá)式匹配的字符串,matches[n]表示于模式中第n個(gè)捕獲組匹配的字符串。
要注意的是,第一,exec()永遠(yuǎn)只返回一個(gè)匹配項(xiàng)(指匹配整個(gè)正則的),第二,如果設(shè)置了g修飾符,每次調(diào)用exec()會(huì)在字符串中繼續(xù)查找新匹配項(xiàng),不設(shè)置g修飾符,對(duì)一個(gè)字符串每次調(diào)用exec()永遠(yuǎn)只返回第一個(gè)匹配項(xiàng)。所以如果要匹配一個(gè)字符串中的所有需要匹配的地方,那么可以設(shè)置g修飾符,然后通過(guò)循環(huán)不斷調(diào)用exec方法。
//匹配所有ing結(jié)尾的單詞
var str="Reading and Writing";
var pattern=/\b([a-zA-Z]+)ing\b/g;
var matches;
while(matches=pattern.exec(str)){
console.log(matches.index +' '+ matches[0] + ' ' + matches[1]);
}
//循環(huán)2次輸出
//0 Reading Read
//12 Writing Writ
3. RegExp上用來(lái)測(cè)試匹配成功與否的方法——test()
var result=pattern.test(str);
接受一個(gè)參數(shù):源字符串
返回:找到匹配項(xiàng),返回true,沒(méi)找到返回false
4. RegExp構(gòu)造函數(shù)屬性
RegExp構(gòu)造函數(shù)包含一些屬性,適用于作用域中的所有正則表達(dá)式,并且基于所執(zhí)行的最近一次正則表達(dá)式操作而變化。
- RegExp.input或RegExp["$_"]:最近一次要匹配的字符串
- RegExp.lastMatch或RegExp["$"]:最近一次匹配項(xiàng)
- RegExp.lastParen或RegExp["$+"]:最近一次匹配的捕獲組
- RegExp.leftContext或RegExp["$`"]:input字符串中l(wèi)astMatch之前的文本
- RegExp.rightContext或RegExp["$'"]:input字符串中l(wèi)astMatch之后的文本
- RegExp["$n"]:表示第n個(gè)捕獲組的內(nèi)容,n取1-9
5. String類型上的模式匹配方法
上面提到的exec和test都是在RegExp實(shí)例上的方法,調(diào)用主體是一個(gè)正則表達(dá)式,而以字符串為主體調(diào)用模式匹配也是最為常用的。
5.1 匹配捕獲的match方法
在字符串上調(diào)用match方法,本質(zhì)上和在正則上調(diào)用exec相同,但是match方法返回的結(jié)果數(shù)組是沒(méi)有input和index屬性的。
var str="Reading and Writing";
var pattern=/\b([a-zA-Z]+)ing\b/g;
//在String上調(diào)用match
var matches=str.match(pattern);
//等價(jià)于在RegExp上調(diào)用exec
var matches=pattern.exec(str);
5.2 返回索引的search方法
接受的參數(shù)和match方法相同,要么是一個(gè)正則表達(dá)式,要么是一個(gè)RegExp對(duì)象。
//下面兩個(gè)控制臺(tái)輸出是一樣的,都是5
var str="I am reading.";
var pattern=/\b([a-zA-Z]+)ing\b/g;
var matches=pattern.exec(str);
console.log(matches.index);
var pos=str.search(pattern);
console.log(pos);
5.3 查找并替換的replace方法
var result=str.replace(RegExp or String, String or Function);
第一個(gè)參數(shù)(查找):RegExp對(duì)象或者是一個(gè)字符串(這個(gè)字符串就被看做一個(gè)平凡的字符串)
第二個(gè)參數(shù)(替換內(nèi)容):一個(gè)字符串或者是一個(gè)函數(shù)
返回:替換后的結(jié)果字符串,不會(huì)改變?cè)瓉?lái)的字符串
第一個(gè)參數(shù)是字符串
只會(huì)替換第一個(gè)子字符串
第一個(gè)參數(shù)是正則
指定g修飾符,則會(huì)替換所有匹配正則的地方,否則只替換第一處
第二個(gè)參數(shù)是字符串
可以使用一些特殊的字符序列,將正則表達(dá)式操作的值插進(jìn)入,這是很常用的。
- $n:匹配第n個(gè)捕獲組的內(nèi)容,n取0-9
- $nn:匹配第nn個(gè)捕獲組內(nèi)容,nn取01-99
- $`:匹配子字符串之后的字符串
- $':匹配子字符串之前的字符串
- $:匹配整個(gè)模式得字符串
- $$:表示$符號(hào)本身
第二個(gè)參數(shù)是一個(gè)函數(shù)
- 在只有一個(gè)匹配項(xiàng)的情況下,會(huì)傳遞3個(gè)參數(shù)給這個(gè)函數(shù):模式的匹配項(xiàng)、匹配項(xiàng)在字符串中的位置、原始字符串
- 在有多個(gè)捕獲組的情況下,傳遞的參數(shù)是模式匹配項(xiàng)、第一個(gè)捕獲組、第二個(gè)、第三個(gè)...最后兩個(gè)參數(shù)是模式的匹配項(xiàng)在字符串位置、原始字符串
這個(gè)函數(shù)要返回一個(gè)字符串,表示要替換掉的匹配項(xiàng)
5.4 分隔字符串的split
基于指定的分隔符將一個(gè)字符串分割成多個(gè)子字符串,將結(jié)果放入一個(gè)數(shù)組,接受的第一個(gè)參數(shù)可以是RegExp對(duì)象或者是一個(gè)字符串(不會(huì)被轉(zhuǎn)為正則),第二個(gè)參數(shù)可選指定數(shù)組大小,確保數(shù)組不會(huì)超過(guò)既定大小。
6 JS(ES5)中正則的局限
JS(ES5)中不支持以下正則特性(在一覽圖中也可以看到):
匹配字符串開(kāi)始和結(jié)尾的\A和\Z錨
向后查找(所以不支持零寬度后發(fā)斷言)
并集和交集類
原子組
Unicode支持(\uFFFF之后的)
命名的捕獲組
單行和無(wú)間隔模式
條件匹配
注釋
四、ES6對(duì)正則的主要加強(qiáng)
ES6對(duì)正則做了一些加強(qiáng),這邊僅僅簡(jiǎn)單羅列以下主要的3點(diǎn),具體可以去看ES6
1. 構(gòu)造函數(shù)可以傳正則字面量了
ES5中構(gòu)造函數(shù)是不能接受字面量的正則的,所以會(huì)有雙重轉(zhuǎn)義,但是ES6是支持的,即便如此,還是建議用字面量創(chuàng)建,簡(jiǎn)潔高效。
2. u修飾符
加了u修飾符,會(huì)正確處理大于\uFFFF的Unicode,意味著4個(gè)字節(jié)的Unicode字符也可以被支持了。
// \uD83D\uDC2A是一個(gè)4字節(jié)的UTF-16編碼,代表一個(gè)字符
/^\uD83D/u.test('\uD83D\uDC2A')
// false,加了u可以正確處理
/^\uD83D/.test('\uD83D\uDC2A')
// true,不加u,當(dāng)做兩個(gè)unicode字符處理
加了u修飾符,會(huì)改變一些正則的行為:
- .原本只能匹配不大于\uFFFF的字符,加了u修飾符可以匹配任何Unicode字符
- Unicode字符新表示法\u{碼點(diǎn)}必須在加了u修飾符后才是有效的
- 使用u修飾符后,所有量詞都會(huì)正確識(shí)別碼點(diǎn)大于0xFFFF的Unicode字符
- 使一些反義元字符對(duì)于大于\uFFFF的字符也生效
3. y修飾符
y修飾符的作用與g修飾符類似,也是全局匹配,開(kāi)始從位置0開(kāi)始,后一次匹配都從上一次匹配成功的下一個(gè)位置開(kāi)始。
不同之處在于,g修飾符只要剩余位置中存在匹配就可,而y修飾符確保匹配必須從剩余的第一個(gè)位置開(kāi)始。
所以/a/y去匹配"ba"會(huì)匹配失敗,因?yàn)閥修飾符要求,在剩余位置第一個(gè)位置(這里是位置0)開(kāi)始就要匹配。
ES6對(duì)正則的加強(qiáng),可以看這篇
五、應(yīng)用正則的實(shí)踐思路
應(yīng)用正則,一般是要先想到正則(廢話),只要看到和“找”相關(guān)的需求并且這個(gè)源是可以被字符串化的,就可以想到用正則試試。
一般在應(yīng)用正則有兩類情況,一是驗(yàn)證類問(wèn)題,另一類是搜索、提取、替換類問(wèn)題。驗(yàn)證,最常見(jiàn)的如表單驗(yàn)證;搜索,以某些設(shè)定的命令加關(guān)鍵詞去搜索;提取,從某段文字中提取什么,或者從某個(gè)JSON對(duì)象中提取什么(因?yàn)镴SON對(duì)象可以字符串化啊);替換,模板引擎中用到。
1. 驗(yàn)證類問(wèn)題
驗(yàn)證類問(wèn)題是我們最常遇到的,這個(gè)時(shí)候其實(shí)源字符串長(zhǎng)什么樣我們是不知道,鬼知道萌萌噠的用戶會(huì)做出什么邪惡的事情來(lái),推薦的方式是這樣的:
- 首先用白話描述清楚你要怎樣的字符串,描述好了之后,就開(kāi)腦洞地想用戶可能輸入什么奇怪的東西,就是自己舉例,拿一張紙可舉一大堆的,有接受的和不接受的(這個(gè)是你知道的),這個(gè)過(guò)程中可能你會(huì)去修改之前的描述;
- 把你的描述拆解開(kāi)來(lái),翻譯成正則表達(dá)式;
- 測(cè)試你的正則表達(dá)式對(duì)你之前舉的例子的判斷是不是和你預(yù)期一致,這里就推薦用在線的JS正則測(cè)試去做,不要自己去一遍遍寫了。
2. 搜索、提取、替換類問(wèn)題
這類問(wèn)題,一般我們是知道源文本的格式或者大致內(nèi)容的,所以在解決這類問(wèn)題時(shí)一般已經(jīng)會(huì)有一些測(cè)試的源數(shù)據(jù),我們要從這些源數(shù)據(jù)中提取出什么、或者替換什么。
- 找到這些手上的源數(shù)據(jù)中你需要的部分;
- 觀察這些部分的特征,這些部分本身的特征以及這些部分周圍的特征,比如這部分前一個(gè)符號(hào)一定是一個(gè)逗號(hào),后一個(gè)符號(hào)一定是一個(gè)冒號(hào),總之就是找規(guī)律;
- 考察你找的特征,首先能不能確切地標(biāo)識(shí)出你要的部分,不會(huì)少也不會(huì)多,然后考慮下以后的源數(shù)據(jù)也是如此么,以后會(huì)不會(huì)這些特征就沒(méi)有了;
- 組織你對(duì)要找的這部分的描述,描述清楚經(jīng)過(guò)你考察的特征;
- 翻譯成正則表達(dá)式;
- 測(cè)試。
終于絮絮叨叨寫完了,1萬(wàn)多字有關(guān)JS正則的講解,寫完發(fā)現(xiàn)自己對(duì)正則的熟練又進(jìn)了一步,所以推薦大家經(jīng)常做做梳理,很有用,然后樂(lè)于分享,于己于人都是大有裨益,感謝能看完的所有人。
我沒(méi)有仔細(xì)地審稿,大家遇到什么問(wèn)題,或者發(fā)現(xiàn)有什么紕漏之處,還望大家指出,留言就好。我會(huì)及時(shí)修改。
您可能感興趣的文章:- Javascript中正則表達(dá)式的使用及基本語(yǔ)法
- 正則表達(dá)式基本語(yǔ)法及表單驗(yàn)證操作詳解【基于JS】
- 詳解js正則表達(dá)式語(yǔ)法介紹
- 老生常談JavaScript 正則表達(dá)式語(yǔ)法
- JavaScript正則表達(dá)式上之基本語(yǔ)法(推薦)
- javascript正則表達(dá)式定義(語(yǔ)法)總結(jié)
- js正則表達(dá)式基本語(yǔ)法(精粹)
- js 玩轉(zhuǎn)正則表達(dá)式之語(yǔ)法高亮
- 正則表達(dá)式語(yǔ)法規(guī)則及在Javascript和C#中的使用方法
- javascript中正則表達(dá)式語(yǔ)法詳解