以前對(duì)于正則是非常懼怕的,因?yàn)榭床欢蛯W(xué)不會(huì)。但最近項(xiàng)目中頻繁的使用到了正則,因此強(qiáng)迫自己去學(xué)習(xí)了解,慢慢的體會(huì)到了他的魅力與強(qiáng)大。當(dāng)然學(xué)習(xí)正則初入門的時(shí)候有些枯燥難懂,但越學(xué)越覺得輕松。本文不準(zhǔn)備說關(guān)于正則本身的事兒,而是說一說關(guān)于javascript中關(guān)于正則的幾個(gè)方法中被很多人忽略的地方。
工具
說到正則,很多人都是從抄到改到自己寫,這個(gè)過程可能有時(shí)候很漫長(zhǎng)。如一些工具能幫助你快速分析和學(xué)習(xí)正則,那么學(xué)習(xí)的過程你肯定要輕松得多。下面我推薦兩個(gè)我經(jīng)常使用的正則在線可視化工具,正則可視化工具圖解符合鐵路圖規(guī)律(其實(shí)不明白什么是鐵路一樣很容易看懂,只是一些細(xì)微的地方和我們的常規(guī)思維有點(diǎn)差別)。
regexper 我最常用的一個(gè),個(gè)人覺得UI做得比其他好
regulex 備選,他有一個(gè)很舒心的功能,可以提供一段js,嵌套到你的網(wǎng)站,生成正則可視化圖
一道小小的題目
這道題目是在群里日常閑聊時(shí),公司同事拋出來的,具體是出自哪里本人沒去考察。先先說說題目:
寫一個(gè)方法把一個(gè)數(shù)字末尾的連續(xù)0變成9,如1230000變成1239999
一道很簡(jiǎn)單的題目,直接正則就能搞定,也許你會(huì)寫:
1234567function zoreToNine(num){return (num + '').replace(/0/g,9);}//或者function zoreToNine(num){return (num + '').replace(/[1-9]0+$/,9);}
這也是此題的陷阱所在,按照上面的方法,1023000就會(huì)被轉(zhuǎn)化成1923999,這樣是不符合要求的,所以改進(jìn)一下:
1234567function zoreToNine(num){return (num + '').replace(/[1-9]0+$/,function($1){return $1.replace(/0/g,9);});}zoreToNine(1223000); //1223999zoreToNine(1023000); //1023999
關(guān)于這個(gè)問題的解決方案@微醺歲月同學(xué)提供了一種,位置匹配的方法,簡(jiǎn)單了很多,厲害!
1"12300100000".replace(/0(?=(0+$)|?)/g,9); //12300199999
當(dāng)然解決問題的方法很多,不一定非要用正則,還完全可以使用純算術(shù)的方法實(shí)現(xiàn),大家有興趣可以嘗試,閑話少說進(jìn)入這次的主題:javascript支持正則表達(dá)式相關(guān)方法,注意并不是正則對(duì)象的方法。
上述方法使用了正則,有趣的是在回調(diào)函數(shù)里有一個(gè)$1,這個(gè)$1到底是什么?所有的匹配規(guī)則匹配后都有$1這個(gè)變量么?…一連串的問題,以前我從來沒有去追探過,趁著昨個(gè)比較空閑,去追探了一番,并在今天整理了一下,寫下此文記錄。
主角
javascript中正則對(duì)象有三個(gè)方法:test、exec和compile,但是此次的主角并不是它們!我們討論的是能夠使用正則表示的相關(guān)方法:search、match、replace和split,注意它們都是String對(duì)象的方法,使用它們必須要是String類型.
replace(rule[regexp/substr], replacement)
replace是一個(gè)用于替換字符串的方法,雖然看似簡(jiǎn)單,但是它隱藏的機(jī)關(guān)也是常常被人忽略。具體分析一下它的特點(diǎn):
它接收兩個(gè)參數(shù)
無副作用不影響原始變量
返回被改變的字符串(一定是字符串類型)
定義一些變量,方便全文取用。
1let a = '12309800', b = '12309800[object Object]', b = '12309800{}';
參數(shù)rule
在一般情況,rule參數(shù)一般是正則、字符串、數(shù)字。
如果是字符串,將會(huì)在匹配到第一個(gè)符合條件的目標(biāo),結(jié)束方法;
如果是正則,則按照正則的規(guī)則進(jìn)行匹配
1234//匹配第一個(gè)0替換成5a.replace(0,5); //'12359800'//匹配所有的0替換成5a.replace(/0/g,5); //'12359855'
參數(shù)replacement
在一般情況,replacement參數(shù)是字符串、數(shù)字、者回調(diào)。
包含$的字符串
當(dāng)參數(shù)rule為正則,并且正則至少包含有一對(duì)完整的()時(shí),如果replacement包含有$的字符串,那么對(duì)于$n(n為大于0的整數(shù),n的長(zhǎng)度取決于正則中括號(hào)的對(duì)數(shù)),會(huì)被解析成一個(gè)變量。但是也僅僅只是作為一個(gè)變量,無法在字符串中進(jìn)行計(jì)算,此時(shí)更類似特別的字符串模板變量。
這篇文章共享之前我仍是要引薦下我自個(gè)的前端群:657137906,不論你是小白仍是大牛,小編我都挺期待,不定期共享干貨,包含我自個(gè)整理的一份2017最新的前端材料和零根底入門教程,期待初學(xué)和進(jìn)階中的小伙伴。
一般情況下,$n中n的長(zhǎng)度取決于正則中括號(hào)的對(duì)數(shù),$1表示第1對(duì)括號(hào)匹配的結(jié)果,$2表示第2對(duì)匹配的結(jié)果…在正則所有的括號(hào)對(duì)中,左括號(hào)出現(xiàn)在第幾個(gè)位置(或者說從左往右),則它就是第幾對(duì)括號(hào),以此類推。姑且我們把這種規(guī)則成為正則匹配分割規(guī)則(ps:這完全是我自己取的一個(gè)名字,方便文章后面使用和記憶)。
123456a.replace(0,'$0'); //'123$09800'a.replace(/00/g,'$0'); //'123098$0'a.replace(/[1-9]0+$/,'$1'); //'12309$1'a.replace(/([1-9](0+$))/,'$1'); //'12309800',此時(shí)$1為[1-9](0+$)匹配到的內(nèi)容,$2為0+$匹配到的內(nèi)容a.replace(/([1-9])(0+$)/,'$1'); //'123098',此時(shí)$1為[1-9]匹配到的內(nèi)容,$2為0+$匹配到的內(nèi)容a.replace(/([1-9])(0+$)/,'$1*$2'); //'123098*00',此處的$1和$2不會(huì)安照期待的情況進(jìn)行乘法計(jì)算,要進(jìn)行計(jì)算可以用回調(diào)
請(qǐng)注意:雖然目前參數(shù)replacement中攜帶有$n仍然能正常使用,但是這種方式已經(jīng)不被規(guī)范所推薦,更應(yīng)該使用回調(diào)來完成這個(gè)操作。這一點(diǎn)謝謝@lucky4同學(xué)的指出
如果正則中包含有全局匹配標(biāo)志(g),那么每次匹配的都符合上述規(guī)則
回調(diào)函數(shù)
先看例子:
123456789101112a.replace(/[1-9]0+$/,function(){console.log(arguments); //["800",5,"12309800"]、});a.replace(/([1-9])0+$/,function(){console.log(arguments); //["800","8",5,"12309800"]});a.replace(/([1-9])(0+$)/,function(){console.log(arguments); //["800","8","00",5,"12309800"]});a.replace(/(([1-9])(0+$))/,function(){console.log(arguments); //["800","800","8","00",5,"12309800"]});
回調(diào)函數(shù)的arguments數(shù)組部分組成:[完整匹配的字符串,$1,$2,…,$n,匹配的開始位置,原始字符串],$1...$n表示每個(gè)括號(hào)對(duì)的匹配,規(guī)則和前面的相同。
所以有一下規(guī)律:
12345let arr = [...arguments], len = arr.length;(len >= 3) === true;arr[0] = 完整匹配的字符串;arr[len-2] = 匹配的開始位置;arr[len-1] = 原始字符串;
注意:除了匹配的開始位置是Number類型外,其余的都是String類型
非常規(guī)類型參數(shù)
如果參數(shù)類型不是上述兩種情況,會(huì)發(fā)生什么呢?看看下面的例子:
12345678a.replace(0,null); //123null9800a.replace(0,undefined); //123null9800a.replace(0,[]); //1239800a.replace(0,Array); //1230,3,123098009800b.replace({},5); //123098005c.replace({},5); //'12309800{}'a.replace(0,{}); //123[object Object]9800a.replace(0,Object); //12309800
由上面的例子可以看出,如果非正則也非字符串,則有以下規(guī)則:
null變量,則會(huì)轉(zhuǎn)換成'null'字符串;
undefined變量,則會(huì)轉(zhuǎn)換成'undefined'字符串;
[]變量,則會(huì)調(diào)用join()方法轉(zhuǎn)換成字符串,默認(rèn)以,分割,值得注意的是空數(shù)組將會(huì)被轉(zhuǎn)換成空字符串(沒有任何字符),通常會(huì)被匹配源字符串的開始位置(默認(rèn)開始位置為空字符串);
‘Array’變量,則會(huì)先轉(zhuǎn)成成一個(gè)匹配的數(shù)組,形如[完整匹配的字符串,$1,$2,...,$n,匹配的開始位置,原始字符串],然后對(duì)它調(diào)用join()方法轉(zhuǎn)換成字符串,默認(rèn)以,分割;
{}變量,則會(huì)調(diào)用Object.protype.toString.call()方法把{}轉(zhuǎn)換成[object Object];
Object變量,則貌似什么都沒做
雖然可以傳入這些非正常參數(shù),但大多數(shù)情況下這些類型的參數(shù)對(duì)實(shí)際是毫無意義的,所以不建議傳入以上類型的參數(shù)。同上面的正則匹配分割規(guī)則一樣,為了方便使用稱呼,姑且我把上面的轉(zhuǎn)換規(guī)則稱為正則匹配參數(shù)轉(zhuǎn)換規(guī)則
match(rule[regex/substr])
match方法可在字符串內(nèi)檢索指定的值,或找到一個(gè)或多個(gè)正則表達(dá)式的匹配。
該方法類似indexOf和lastIndexOf,但是它返回指定的值,而不是字符串的位置;
參數(shù)
參數(shù)的傳遞除了常規(guī)的正則和字符串以外,其余所有類型的參數(shù)都會(huì)按照上述的正則匹配參數(shù)轉(zhuǎn)換規(guī)則轉(zhuǎn)換成字符串形式來匹配。
返回值
返回值根據(jù)傳入的參數(shù)類型和規(guī)則的不同,返回的內(nèi)容不同,但總體來說,它是返回一個(gè)對(duì)象,而不是索引,如果沒匹配到任何符合條件的字符串,則返回null。
非全局匹配正則
如果匹配規(guī)則是一個(gè)非全局匹配規(guī)則,那么,它此時(shí)的返回值是一個(gè)偽數(shù)組對(duì)象(likeArr),形如:[一個(gè)展開的匹配到的字符串?dāng)?shù)組, 匹配到的字符串位置, 原始字符串],它有如下規(guī)律:
12345var likeArr = a.match(regex);likeArr[0] = 匹配到的字符串;likeArr[1...n] = 正則匹配分割規(guī)則匹配的字符串;likeArr.index = 匹配到字符串的位置likeArr.inupt = 原始字符串
看例子:
1234a.match(/[1-9]0+$/); //[0:'800',index:5,input:'12309800']a.match(/([1-9])0+$/); //[0:'800',1:'8',index:5,input:'12309800']a.match(/[1-9](0+$)/); //[0:'800',1:'00',index:5,input:'12309800']a.match(/([1-9])(0+$)/); //[0:'800',1:'8',2:'00',index:5,input:'12309800']
全局匹配正則
如果匹配規(guī)則是一個(gè)全局匹配規(guī)則(正在攜帶有g(shù)標(biāo)志),那么,它此時(shí)的返回值是一個(gè)數(shù)組對(duì)象(arr),形如:[匹配到的字符串?dāng)?shù)1,匹配到的字符串?dāng)?shù)2,匹配到的字符串?dāng)?shù)3];
看例子:
12a.match(/[1-9]0/); //[0:'30',index:2,input:'12309800']a.match(/[1-9]0/g); //[0:'30',1:'80']
search(rule[regex/substr])
search方法用于檢索字符串中指定的子字符串,或檢索與正則表達(dá)式相匹配的子字符串。
stringObject中第一個(gè)與rule相匹配的子串的起始位置。如果沒有找到任何匹配的子串,則返回-1。
注意:
search方法不執(zhí)行全局匹配,它將忽略標(biāo)志g。
忽略regexp的lastIndex屬性,總是從字符串的開始進(jìn)行檢索,這意味著它總是返回stringObject的第一個(gè)匹配的位置
同樣,search可以傳入任何參數(shù)類型,它會(huì)遵循正則匹配參數(shù)轉(zhuǎn)換規(guī)則進(jìn)行轉(zhuǎn)換
split(rule[regex/substr],len)
這個(gè)方法就不用多說,很常用的字符串分割方法。
第二個(gè)參數(shù)的作用就是限制返回值的長(zhǎng)度,表示返回值的最大長(zhǎng)度
當(dāng)然,它依然可以傳入任何參數(shù)類型,會(huì)遵循正則匹配參數(shù)轉(zhuǎn)換規(guī)則進(jìn)行轉(zhuǎn)換
有一段加密的后的密碼,我們需要分離出字符串’12a344gg333tt445656ffa6778ii99’中的前三組數(shù)字,通過某種計(jì)算才能得出正確的密碼
1'12a344gg333tt445656ffa6778ii99'.split(/[a-zA-Z]+/g,3);//['12','334','333']
最后
寫了這么多,突然發(fā)現(xiàn)以前僅僅是在用這些方法,了解得很不夠深入。越是學(xué)習(xí)才發(fā)現(xiàn)其中的奧秘!學(xué)無止境,與諸君共勉!
以上內(nèi)容如有錯(cuò)誤之處,希望諸君不吝指出!