注:參考《JavaScript語言精粹》第七章和第八章。
一、正則表達(dá)式的概念,作用
1.概念:正則表達(dá)式是一門簡單語言的語法規(guī)范邓梅。
2.作用:它以方式的形式被用于對(duì)字符串中的信息進(jìn)行查找,替換和提取的操作邑滨。js中可處理正則表達(dá)式的方式有regexp.exec()
,regexp.test()
,string.match()
,string.replace()
,string.search()
和string.split()
日缨。關(guān)于這些方法將在本篇最后一節(jié)詳述。通常來說掖看,在js中正則表達(dá)式相較于等效的字符串運(yùn)算有著顯著的性能優(yōu)勢匣距。
二、學(xué)習(xí)正則表達(dá)式
我先大體介紹下正則表達(dá)式的結(jié)構(gòu)以及創(chuàng)建方式(2.1小節(jié))乙各,然后把整個(gè)正則表達(dá)式拆開來看墨礁,分別在之后的小節(jié)介紹。最后再組合在一起舉個(gè)小例子(第三節(jié))大概就能逐漸清晰起來耳峦。
2.1 正則表達(dá)式結(jié)構(gòu)恩静,創(chuàng)建方式
1.創(chuàng)建方式(2種):
① 正則表達(dá)式字面量(性能更好,優(yōu)先使用)
正則表達(dá)式字面量被包圍在一對(duì)斜杠中,例如:
let regex = /abc+/; //{1}
let regex = /[a-z]+_[0-9]+/gi; //{2}
{1}中展示了命名正則表達(dá)式的字面量寫法蹲坷,ab+c
這一個(gè)正則表達(dá)式被包裹在一對(duì)斜杠中驶乾。
{2}中的正則表達(dá)式字面量的斜杠后有兩個(gè)字母:gi
,這里說明一下循签,它們是正則表達(dá)式的匹配模式的標(biāo)志级乐。一種有3個(gè)匹配模式的標(biāo)志能在RegExp中設(shè)置,他們分別是:g
县匠、i
风科、m
。它們代表的意思如下:
標(biāo)志 | 含義 |
---|---|
g | 全局匹配(對(duì)一個(gè)字符串進(jìn)行多次匹配乞旦,準(zhǔn)確含義隨方法而變) |
i | 匹配時(shí)大小寫不敏感(忽略大小寫) |
m | 多行匹配(^和$能匹配行結(jié)束符) |
這里來舉個(gè)例子來分別說明一下(說明贼穆,我使用的測試方法是JavaScript中String的match
方法,使用瀏覽器的console作為代碼運(yùn)行環(huán)境):
let regex1 = /[a-z]+_[0-9]+/; //{1}
let regex2 = /[a-z]+_[0-9]+/g; //{2}
let regex3 = /[a-z]+_[0-9]+/i; //{3}
let str = "Yaya_1 jerry_3 Tom_2 leo_4"; //{4}
{1}是匹配 至少一個(gè)任意小寫字母 加 一個(gè)下劃線 加 至少一個(gè)任意數(shù)字(如abc_123) 這樣的形式兰粉。{2},{3}相較于{1}分別是多了g
,i
的標(biāo)志(m
標(biāo)志的例子在后邊)故痊。
那么拿{4}的字符串分別與{1},{2},{3}匹配,則匹配結(jié)果分別如下圖所示:
let regex1 = /^[a-z]+_[0-9]+/; //{1}
let regex2 = /^[a-z]+_[0-9]+/g; //{2}
let regex3 = /^[a-z]+_[0-9]+/m; //{3}
let str1 = "frank_5 tony_6 jason_7"; //{4}
let str2 = ["I am the first line", "frank_5 tony_6 jason_7"].join('\n'); //{5}
② 使用RegExp構(gòu)造器(不推薦)
這個(gè)構(gòu)造器會(huì)接收一個(gè)字符串玖姑,并把它編譯為一個(gè)RegExp對(duì)象愕秫。第二個(gè)參數(shù)接收指定標(biāo)志的字符串,例如:
let regex = new RegExp("abc+");
let regex = new RegExp(/^[a-zA-Z]+[0-9]*\W?_$/, "gi"); //{1}
let regex = new RegExp("^[a-zA-Z]+[0-9]*\\W?_$", "gi"); //{2}
這里需要注意焰络,因?yàn)榉葱备茉谡齽t表達(dá)式和在字符串字面量中有一些不同的含義戴甩,所以在書寫時(shí)需要小心,通常情況下需要雙寫反斜杠舔琅,請(qǐng)看{1}{2}的區(qū)別等恐。所以通常情況下不推薦使用RegExp構(gòu)造器這種方式來創(chuàng)建一個(gè)正則表達(dá)式。
接下來我們開始詳細(xì)拆分講述正則表達(dá)式。
2.2 正則表達(dá)式分支
正則表達(dá)式分支也就是“或”的意思课蔬,用符號(hào)|表示囱稽。如果這些分支條件中有任何一項(xiàng)滿足匹配條件,那么就符合二跋。例如以下代碼能匹配first或者second战惊,所以不管是{1}還是{2},該正則表達(dá)式都能匹配到(注意這個(gè)正則有個(gè)i的標(biāo)志扎即,所以匹配的時(shí)候會(huì)忽略大小寫)吞获。
let regex = /first|second/i;
let str1 = "First, I will tell you …"; //{1}
let str2 = "and second, I will …"; //{2}
這里的{1},{2}都有一個(gè)子字符串會(huì)被匹配,如圖所示谚鄙。
2.3 正則表達(dá)式因子
正則表達(dá)式拆開來看也就是很多個(gè)因子和量詞的組合各拷,因子是決定該正則需要匹配的字符都可以有哪些,而量詞是決定該量詞前面的因子可以匹配多少次(具體見2.7小節(jié))闷营。
而正則表達(dá)式因子可以是這四種類型:
1.一個(gè)字符(除了特殊字符之外的任意普通字符烤黍,如a
、b
傻盟、c
速蕊、0
、1
娘赴、2
规哲、#
、%
诽表、&
等等)
2.一個(gè)正則轉(zhuǎn)義(詳見2.5節(jié))
3.一個(gè)字符類(詳見2.4節(jié))
4.一個(gè)由圓括號(hào)包圍的組(詳見2.6節(jié))
2.4 正則表達(dá)式字符類
1.概念:正則表達(dá)式字符類是一種指定一組字符的便利方式唉锌,也就是說正則在匹配的時(shí)候可以匹配這個(gè)字符類中的任意一個(gè)字符都可以。例如想匹配任意元音字母中的任意一個(gè)字符竿奏,我們可以寫成a|e|i|o|u
糊秆,但通常我們可以更方便的寫成一個(gè)類:[aeiou]
2.類的兩個(gè)好處:
1.可以指定字符范圍:例如/[a-z]+_[0-9]+/
,我們可以方便的指定a-z
议双,0-9
,(這里的-
代表范圍的意思捉片,指az,09)但如果用分支的話就要麻煩了平痰。
2.方便類的求反:如果要匹配除了[a-z]
之外的所有字符,則可以用字符類方便的寫為[^a-z]
,那么這里字符類中的^
則代表取反的意思
3.注意:在字符類中需要被轉(zhuǎn)義的特殊字符:-
/
[
\
]
^
2.5 正則表達(dá)式轉(zhuǎn)義
正則表達(dá)式中一些轉(zhuǎn)義字符存在特殊的含義伍纫,以下列出一些常用的:
轉(zhuǎn)義字符 | 含義 |
---|---|
\d | 匹配一個(gè)數(shù)字宗雇。 等價(jià)于[0-9] |
\D | 與\d相反。匹配一個(gè)非數(shù)字字符莹规。等價(jià)于[^0-9] |
\f | 匹配一個(gè)換頁符 |
\n | 匹配一個(gè)換行符 |
\r | 匹配一個(gè)回車符 |
\s | 等同于 [\f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]赔蒲。是Unicode空白符的不完全子集。 |
\S | 與\s相反。等同于[^\f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]舞虱,表示非空白字符欢际。 |
\t | 匹配一個(gè)水平制表符 |
\v | 匹配一個(gè)垂直制表符 |
\w | 等同于[0-9A-Z_a-z]。 匹配一個(gè)單字字符(字母矾兜、數(shù)字或者下劃線)损趋。 |
\W | 與\w相反。等同于[^0-9A-Z_a-z] |
\b | 匹配一個(gè)詞的邊界椅寺。它是利用\w去尋找邊界的 |
\n(注:這里的n代表一個(gè)數(shù)字) | 在正則表達(dá)式中浑槽,它代表正則中的第n個(gè)組所對(duì)應(yīng)的最后捕獲到的子字符串(關(guān)于組的概念詳見2.5小節(jié)) |
2.6 正則表達(dá)式分組
因子的前3種類型(見2.2小節(jié))都是匹配一個(gè)字符,但分組可以匹配多個(gè)字符返帕,也就是說分組可以匹配字符串桐玻。
正則表達(dá)式分組有4種類型(注:以下x,y都代表一小段正則表達(dá)式):
1.捕獲型:捕獲型分組是這樣的格式:(x)
荆萤,也就是說一個(gè)捕獲型分組是一個(gè)被包圍在圓括號(hào)中的一小段正則表達(dá)式镊靴。之所以為捕獲型,是因?yàn)槿魏纹ヅ溥@個(gè)分組的字符都會(huì)被捕獲到观腊。每個(gè)捕獲型分組都會(huì)有一個(gè)數(shù)字代號(hào)邑闲,很簡單:第1個(gè)捕獲的分組就是分組1,第2個(gè)捕獲的分組是分組2梧油,以此類推苫耸。
2.非捕獲型:非捕獲型分組是這樣的格式:(?:x)
,也就是說與捕獲型分組相比儡陨,它的圓括號(hào)中有一個(gè)?:
的前綴褪子。非捕獲型分組僅做簡單的匹配,并不會(huì)捕獲匹配的文本骗村。
3.向前正向匹配:向前正向匹配是這樣的格式:x(?=y)
嫌褪,匹配'x'僅僅當(dāng)'x'后面跟著’y’,但是注意胚股,它的意思只是檢查x后邊跟的是不是y笼痛,如果是y,那么匹配x琅拌,而y并沒有被真正匹配缨伊。但y才是真正的分組中的內(nèi)容,也就是說這個(gè)分組什么都沒有匹配进宝,只是一個(gè)檢驗(yàn)的作用刻坊。這并不是一個(gè)好的特性,不常用党晋。
4.向前負(fù)向匹配:向前負(fù)向匹配是這樣的格式:x(?!y)
谭胚,匹配'x'僅僅當(dāng)'x'后面不跟著’y’徐块,但是注意,它的意思只是檢查x后邊跟的是不是y灾而,如果不是y胡控,那么匹配x,而y并沒有被真正匹配绰疤。但y才是真正的分組中的內(nèi)容铜犬,也就是說這個(gè)分組什么都沒有匹配,只是一個(gè)檢驗(yàn)的作用轻庆。這并不是一個(gè)好的特性癣猾,不常用。
2.7 正則表達(dá)式量詞
1.解釋:量詞后綴用來決定前面的因子應(yīng)該被匹配的次數(shù)余爆。
2.寫法:通常情況下量詞的寫法是將數(shù)字或者數(shù)字范圍包裹在一對(duì)大括號(hào)中纷宇。如{3,5}
表示最少3次最多5次;{3,}
表示最少3次蛾方;{3}
表示3次像捶。還有一些特殊字符也可以代表匹配次數(shù):?
相當(dāng)于{0,1}
;*
相當(dāng)于{0,}
桩砰;+
相當(dāng)于{1,}
拓春。如果一個(gè)因子后邊沒有量詞后綴,則默認(rèn)匹配該因子1次亚隅。
三硼莽、一個(gè)例子
var parse_url = /^(?:(http|ftp|https):)?(?:\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
我們把該正則表達(dá)式拆開來看:
^
匹配字符串開始的地方,表示該字符串開始煮纵。
(?:(http|ftp|https):)?
匹配一個(gè)非捕獲型分組:在這個(gè)非捕獲分組里又有一個(gè)捕獲分組懂鸵,這個(gè)分組匹配的內(nèi)容是http或ftp或https。捕獲分組結(jié)束后再匹配一個(gè)冒號(hào)行疏,?代表這個(gè)它前面的因子(也就是那個(gè)非捕獲分組)可以被匹配0次或1次匆光。那么字符串http:
或ftp:
或https:
或空字符串都能與這一小段正則匹配。
(?:\/{0,3})
匹配一個(gè)非捕獲型分組:匹配內(nèi)容是字符串/
酿联,由于/
是特殊字符终息,所以需要轉(zhuǎn)義。/
這個(gè)因子被匹配的次數(shù)可以是0次到3次贞让。那么字符串/
或//
或///
或空字符串都能與這一小段正則匹配采幌。
([0-9.\-A-Za-z]+)
匹配一個(gè)捕獲型分組:匹配內(nèi)容是由一個(gè)字符類組成的,匹配字符可以是字符類中的這些字符:0-9
.
-
A-Z
a-z
震桶。這個(gè)字符類被匹配的次數(shù)可以是1次到多次。那么例如字符串a.b_c
a
0_e
等等很多都能與這一小段正則匹配征绎。這一小段在整個(gè)正則中是用來匹配域名或ip地址(host)的蹲姐,例如www.baidu.com
(?::(\d+))?
匹配一個(gè)非捕獲分組:先匹配一個(gè)冒號(hào):
磨取,然后匹配至少一個(gè)數(shù)字的捕獲型分組。?
代表這個(gè)非捕獲型分組的匹配次數(shù)可以是0次或1次柴墩。這一小段在整個(gè)正則中是用來匹配端口號(hào)(port)的忙厌。
(?:\/([^?#]*))?
匹配一個(gè)非捕獲分組:先匹配一個(gè)/
,之后的字符類[^?#]
以^
開始江咳,它表示這個(gè)字符類包含除了?
和#
之外的所有字符逢净,*
表示這個(gè)字符集可以被匹配0次或多次。?
表示這個(gè)非捕獲分組會(huì)匹配0次或1次歼指。這一小段是用來匹配url中的路徑(path)的爹土。
(?:\?([^#]*))?
匹配一個(gè)非捕獲分組:先匹配一個(gè)?
,之后的字符類[^#]
以^
開始踩身,它表示這個(gè)字符類包含除了#
之外的的所有字符胀茵,*
表示這個(gè)字符集可以被匹配0次或多次。?
表示這個(gè)非捕獲分組會(huì)匹配0次或1次挟阻。這一小段是用來匹配url中的參數(shù)(query)的琼娘。
(?:#(.*))?
匹配一個(gè)以#
開始的可選分組,.
會(huì)匹配除行結(jié)束符之外的所有字符附鸽。這一小段是用來匹配url中的錨(hash)的脱拼。
$
匹配字符串結(jié)束的地方。表示該字符串結(jié)束坷备。
四熄浓、js對(duì)于正則表達(dá)式的支持
4.1 regexp.exec(string)
exec
方法是使用正則表達(dá)式最強(qiáng)大(和最慢)的方法。如果它成功的匹配regexp和字符創(chuàng)string击你,它會(huì)返回一個(gè)數(shù)組玉组,數(shù)組下標(biāo)為0的元素將包含正則表達(dá)式regexp匹配的子字符串。下標(biāo)為1的元素是第一個(gè)捕獲分組匹配的文本丁侄,下標(biāo)為2的元素是第二個(gè)捕獲分組匹配的文本惯雳,以此類推。如果匹配失敗鸿摇,他會(huì)返回null石景。
如果regexp帶有g(shù)標(biāo)識(shí),那么查找不是從這個(gè)字符串的起始位置開始的拙吉,而是從regexp.lastIndex
(初始值為0)的位置開始的炸裆。如果匹配成功,那么regexp.lastIndex
將被設(shè)置為該匹配后第一個(gè)字符的位置勺良。不成功的匹配會(huì)重置regexp.lastIndex
為0刊头。這就說明當(dāng)regexp帶有g(shù)標(biāo)識(shí)時(shí),你可以循環(huán)調(diào)用exec
佛舱。但有兩件事情需要注意:1.如果提前退出了這個(gè)循環(huán)椎例,再次進(jìn)入循環(huán)前要把regexp.lastIndex
重置到0挨决;2.^
僅匹配regexp.lastIndex
為0的情況。
例子:
4.2 regexp.test(string)
test
方法是使用正則表達(dá)式最簡單(和最快)的方法订歪。如果該regexp匹配string脖祈,則返回true,否則返回false刷晋。注意:不要對(duì)這個(gè)方法使用g標(biāo)識(shí)盖高,原因是g標(biāo)識(shí)每次會(huì)改變regexp.lastIndex
的值,見下圖:
4.3 string.match(regexp)
match
方法讓字符串和一個(gè)正則表達(dá)式進(jìn)行匹配眼虱。它根據(jù)g標(biāo)識(shí)來決定如何進(jìn)行匹配喻奥。如果沒有g(shù)標(biāo)識(shí),那么調(diào)用string.match(regexp)
的結(jié)果與調(diào)用regexp.exec(string)
的結(jié)果相同蒙幻。如果regexp
帶有g(shù)標(biāo)識(shí)映凳,那么它生成一個(gè)包含所有匹配(除捕獲分組之外)的數(shù)組。
4.4 string.replace(searchValue, replaceValue)
replace
方法對(duì)string
進(jìn)行查找和替換操作邮破,并返回一個(gè)新的字符串诈豌。searchValue
可以是一個(gè)字符串或一個(gè)正則表達(dá)式對(duì)象。如果他是一個(gè)字符串抒和,那么searchValue
只會(huì)在第1次出現(xiàn)的地方被替換矫渔。所以下邊代碼的結(jié)果是:you-are_beautiful
var result = "you_are_beautiful".replace('_', '-');
如果searchValue
是一個(gè)正則表達(dá)式并且?guī)в術(shù)標(biāo)識(shí),那么他會(huì)替換所有的匹配摧莽。如果沒有帶有g(shù)標(biāo)識(shí)庙洼,那么它僅僅替換第一個(gè)匹配。
replaceValue
可以是一個(gè)字符串镊辕,也可以是一個(gè)自定義函數(shù)油够。如果replaceValue
是一個(gè)字符串,那么字符$擁有特別的含義:
var oldareacode = /\((\d{3})\)/g;
var p = '(555)666-1234'.replace(oldareacode, '$1-'); //p 是 '555-666-1234'
如果replaceValue
是一個(gè)函數(shù)征懈,那么每遇到一次匹配函數(shù)就會(huì)被調(diào)用一次石咬,該函數(shù)返回的字符串會(huì)被用作替換文本。傳遞給該函數(shù)的第一個(gè)參數(shù)數(shù)被匹配的文本卖哎,第二個(gè)參數(shù)是分組1捕獲的文本鬼悠,第三個(gè)參數(shù)是分組2捕獲的文本,以此類推亏娜。
4.5 string.search(regexp)
search
方法和indexOf
方法類似焕窝,只是它接受一個(gè)正則表達(dá)式作為參數(shù)而不是一個(gè)字符串。如果找到匹配维贺,它返回第1個(gè)匹配的首字符位置它掂,如果沒有找到匹配,它返回-1.此方法會(huì)忽略g標(biāo)識(shí)溯泣。
var text = 'and he says "Any damn fool could';
var pos = text.search(/["']/); // pos 是12
4.6 string.split(separator, limit)
split
方法把這個(gè)string分割成片段來創(chuàng)建一個(gè)字符串?dāng)?shù)組群发∥希可選參數(shù)limit
可以限制被分隔的片段數(shù)量。separator
可以是一個(gè)字符串或一個(gè)正則表達(dá)式熟妓。此方法會(huì)在string中查找所有separator出現(xiàn)的地方,以此作為分隔符栏尚。此方法會(huì)忽略g標(biāo)識(shí)起愈,加不加g標(biāo)識(shí)都一樣。
var ip = '192.168.0.1';
var b = ip.split('.'); // b是['192','168','0','1']