一海诲、正則初體驗(yàn)
在軟件開發(fā)中躬络,不管是Java、C#坯沪、JS悴灵、OC....基本上都會(huì)接觸到正則扛芽,不過大多數(shù)人都對正則并不是很了解,正常情況下都是直接從網(wǎng)上扒一段(我之前也一直都是這么干的积瞒,哈哈)川尖。但是做開發(fā)時(shí)間比較久之后,再遇到正則就特別想深入的去了解茫孔,而且聽不少朋友說叮喳,最近面試的時(shí)候遇到不少關(guān)于正則問題......
1.1、什么是正則
簡單來說缰贝,正則就可以理解為一個(gè)規(guī)則馍悟,用來處理字符串的一個(gè)規(guī)則(正則就是用來處理字符串的),這里所說處理一般是包含匹配和捕獲剩晴。
- 匹配:判斷一個(gè)字符串是否符合指定的規(guī)則锣咒,使用test方法:reg.test(str)
var reg = /\d/; // 包含一個(gè)0~9之間的數(shù)字
console.log(reg.test('天')); // false
console.log(reg.test('1')); // true
console.log(reg.test('現(xiàn)在是2017')); // true,只要包含了數(shù)字就返回true
- 捕獲:把字符串中符合指定的正則規(guī)則的內(nèi)容捕獲到赞弥,使用exec方法:reg.exec(str)
var reg = /\d/;
console.log(reg.exec('天')); // null
console.log(reg.exec('1')); // ["1", index: 0, input: "1"]
console.log(reg.exec('現(xiàn)在是2017')); // ["2", index: 3, input: "現(xiàn)在是2017"]
1.2毅整、如何創(chuàng)建正則
- 字面量方式
var reg = /\d/;
- 實(shí)例方式
var reg = new RegExp('/\d/');
數(shù)組等對象的字面量方式創(chuàng)建和實(shí)例方式創(chuàng)建出來的數(shù)組差別并不是很大,但是正則的這兩種方式創(chuàng)建出來是有挺大的區(qū)別的绽左。具體是哪些區(qū)別悼嫉,現(xiàn)在說還不合適,先賣個(gè)關(guān)子拼窥,接下來的內(nèi)容中講解戏蔑。
二、正則的組成
從上面的內(nèi)容中已經(jīng)了解到闯团,每一個(gè)正則表達(dá)式是包含在//
中的,正則表達(dá)式就是匹配規(guī)則仙粱,正則的組成就是元字符和修飾符房交。
2.1、元字符
-
具有特殊意義的元字符
- \:轉(zhuǎn)義字符伐割,轉(zhuǎn)義后面字符所代表的含義
- ^:以某一個(gè)元字符開始
- $:以某一個(gè)元字符結(jié)束
- \n:匹配一個(gè)換行符
- .:除了\n以外的任意字符
var reg = /^0.2$/; // 以0開頭候味,以2結(jié)尾刃唤,中間可以是除了\n的任意字符 console.log(reg.test('0.2')); // true console.log(reg.test('0-2')); // true reg = /^0\.2$/; // 將"."轉(zhuǎn)義 console.log(reg.test('0.2')); // true console.log(reg.test('0-2')); // false
-
代表出現(xiàn)次數(shù)的量詞元字符
- *:出現(xiàn)0到多次
- +:出現(xiàn)1到多次
- ?:出現(xiàn)0次或者1次
- {n}:出現(xiàn)n次
- {n,m}:出現(xiàn)n到m次
var reg = /^\d+$/;
console.log(reg.test('2015')); // true
2.2、修飾符
- x|y:x或y中的一個(gè)
- [xyz]:x或y或z中的一個(gè)
- [^xyz]:除了xyz以外的任意一個(gè)字符
- [a-z]:a-z之間的任何一個(gè)字符
- [^a-z]:除了a-z之間的任何一個(gè)字符
- \d:一個(gè)0~9之間的數(shù)字
- \D:除了0~9之間的數(shù)字以外的任何字符
- \b:一個(gè)邊界符
- \w:數(shù)字白群、字母尚胞、下劃線中的任意一個(gè)字符
- \s:匹配一個(gè)空白字符、空格
-
():分組帜慢,把一個(gè)大正則本身劃分成幾個(gè)小的正則笼裳,例如:
var reg = /^(\d+)zhufeng(\d+)$/;
三、元字符的應(yīng)用
在做元字符的應(yīng)用前粱玲,有必要先了解下中括號和分組的使用躬柬,然后能更好的做應(yīng)用。
3.1抽减、[]的規(guī)律
- 在中括號中出現(xiàn)的所有的字符都是代表本身的意思的字符(沒有特殊含義)
var reg = /^[.]$/;
console.log(reg.test('1')); // false
console.log(reg.test('.')); // true
reg = /^[\w-]$/; // 數(shù)字允青、字母、下劃線卵沉、- 中的一個(gè)
console.log(reg.test('-')); // true
- 中括號不識別兩位數(shù)
var reg = /^[12]$/; // --> 1或者2中的一個(gè)(符合[xyz])
var reg = /^[12-68]$/; // --> 1颠锉、2-6中的一個(gè)、8 三個(gè)中的一個(gè)
3.2史汗、()的作用
分組的作用有很多琼掠,現(xiàn)在先講其中的一個(gè):改變x|y的默認(rèn)的優(yōu)先級,還有的在后面的內(nèi)容會(huì)詳細(xì)介紹淹办。
var reg = /^18|19$/; // 18眉枕、19、181怜森、189速挑、119、819副硅、1819這些都符合
var reg = /^(18|19)$/; // 只能18或者19
3.3姥宝、應(yīng)用一:有效數(shù)字的正則
有效數(shù)字可以是正數(shù)、負(fù)數(shù)恐疲、零腊满、小數(shù),所以其特點(diǎn)為:
"."可以出現(xiàn)也可以不出現(xiàn)培己,一旦出現(xiàn)碳蛋,后面必須跟著一位或多為數(shù)字;
最開始可能有“+/-”省咨,也可以沒有肃弟;
整數(shù)部分,一位數(shù)的情況可以是0-9中的一個(gè),多位數(shù)的情況下不能以0開頭笤受;
var reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/;
3.4穷缤、應(yīng)用二:年齡介于18~65之間
年齡介于18~65之間的數(shù)字可以是18-19、20-59箩兽、60-65津肛。
var reg = /^1[8-9]|[2-5]\d|6[0-5]$/;
3.5、應(yīng)用三:簡單的郵箱驗(yàn)證
var reg = /^[\w.-]+@[0-9a-zA-Z]+(\.[a-zA-Z]{2,4}){1,2}$/;
四汗贫、兩種方式創(chuàng)建正則的區(qū)別
在開始的時(shí)候身坐,我們介紹了創(chuàng)建正則有兩種方式:字面量方式、對象方式芳绩。在字面量方式中掀亥,"http://"之間包起來的所有的內(nèi)容都是元字符,有的具有特殊的意義妥色,大部分都是代表本身含義的普通元字符搪花。
現(xiàn)在有這么一個(gè)場景,就是正則中的某一段內(nèi)容是不固定的嘹害,那么我們用字面量的方式可能會(huì)這么寫:
var name = 'iceman';
var reg = /^\d+"+name+"\d+$/;
console.log(reg.test('2015iceman2016')); // false
console.log(reg.test('2015"""nameeee"2016')); // true
我們的想法很美好的撮竿,假設(shè)name是動(dòng)態(tài)設(shè)置的,內(nèi)容為“iceman”笔呀,那么'2015iceman2016'是肯定能適配reg的啊幢踏,但結(jié)果卻是false。
第二條輸出中卻是true许师!是不是很崩潰呢房蝉? 不過再認(rèn)真看一下,我們在第二條匹配的字符串中寫了三個(gè)引號微渠,name的后面再加了三個(gè)e搭幻。看到這里逞盆,是不是發(fā)現(xiàn)了什么呢:
沒錯(cuò)檀蹋,在字面量方式創(chuàng)建的正則中,引號和單獨(dú)出現(xiàn)的加號都被當(dāng)成了普通的元字符云芦。
對于上面的這個(gè)需求俯逾,我們只能使用實(shí)例創(chuàng)建正則的方式:
var name = 'iceman';
var reg = new RegExp("^\\d+" + name + "\\d+$", "g");
console.log(reg.test('2015iceman2016')); // true
所以總結(jié)字面量方式和實(shí)例方式創(chuàng)建正則的區(qū)別:
字面量方式中出現(xiàn)的一切都是元字符,不能進(jìn)行變量值的拼接舅逸,而實(shí)例創(chuàng)建的方式可以桌肴;
字面量方式中直接寫\d可以,而在實(shí)例中需要把它轉(zhuǎn)義 \\d
五琉历、正則的捕獲及其貪婪性和懶惰性
在上面有介紹到正則的捕獲使用exec方法坠七。在每一次捕獲的時(shí)候都是先進(jìn)行默認(rèn)的匹配,如果沒有匹配成功,則捕獲的結(jié)果是null灼捂。只有有匹配的內(nèi)容,才能捕獲到换团。
5.1悉稠、懶惰性
定義正則及字符串:
var reg = /\d+/;
var str = 'iceman2016learn2017';
reg默認(rèn)有一個(gè)lastIndex字段,該字段是正則每一次捕獲時(shí)艘包,在字符串中開始查找的位置的猛,默認(rèn)的值是0。
現(xiàn)在先進(jìn)行第一次捕獲:
console.log(reg.lastIndex); // 0想虎,第一次捕獲的時(shí)候卦尊,從字符串索引0處開始查找
var res = reg.exec(str);
console.log(res); // ["2016", index: 6, input: "iceman2016learn2017"]
從代碼的輸出可知,正則捕獲的內(nèi)容格式:捕獲到的內(nèi)容是一個(gè)數(shù)組
- 數(shù)組的第一項(xiàng)是當(dāng)前大正則捕獲的內(nèi)容舌厨;
- 有一項(xiàng)是index:捕獲內(nèi)容在字符串中開始的索引位置岂却;
- 有一項(xiàng)是input:捕獲的原始字符串;
現(xiàn)在進(jìn)行第二次捕獲:
console.log(reg.lastIndex); // 0 說明第二次捕獲的時(shí)候裙椭,也要從字符串索引0處開始查找
// 第二次通過exec捕獲的內(nèi)容還是第一個(gè)"2016"
res = reg.exec(str);
console.log(res); //["2016", index: 6, input: "iceman2016learn2017"]
由上面的兩次捕獲可知躏哩,每次的捕獲都是從字符串的索引0處開始查找的,這就是正則的懶惰型揉燃。
正則懶惰型的特點(diǎn):每一次執(zhí)行exec只捕獲第一個(gè)匹配的內(nèi)容扫尺,在不進(jìn)行任何處理的情況下,在執(zhí)行多次捕獲的時(shí)候炊汤,捕獲的還是第一個(gè)匹配的內(nèi)容正驻。
很明顯正則的懶惰性是我們所要解決的問題,那么該如何解決懶惰性呢抢腐? 答案就是在正則的末尾加一個(gè)修飾“g”(全局匹配)姑曙,類似g這樣的修飾符還有兩個(gè):i、m氓栈,這三者的作用是:
global(g):全局匹配
ignoreCase(i):忽略大小寫
multiline(m):多行匹配
var reg = /\d+/g;
var str = 'iceman2016learn2017';
console.log(reg.lastIndex); // 0
console.log(reg.exec(str)); // ["2016", index: 6, input: "iceman2016learn2017"]
console.log(reg.lastIndex); // 10
console.log(reg.exec(str)); // ["2017", index: 15, input: "iceman2016learn2017"]
console.log(reg.lastIndex); // 19
console.log(reg.exec(str)); // null
在加了修飾符g之后渣磷,就解決了懶惰型,達(dá)到了我們想要的效果授瘦,所以全局修飾符g的原理是:正則每一次捕獲結(jié)束后醋界,lastIndex的值都變成了最新的值,下一次捕獲從最新的位置開始查找提完,這樣就可以把所有需要捕獲的內(nèi)容都獲取到了形纺。
自己編寫程序獲取正則捕獲的所有內(nèi)容(正則一定要加g哦!M叫馈V鹧)
var reg = /\d+/g;
var str = 'iceman2016learn2017';
var ary = [];
var res = reg.exec(str);
while (res) {
ary.push(res[0]);
res = reg.exec(str);
}
console.log(ary);
5.2、貪婪性
var reg = /\d+/g; // 出現(xiàn)一到多個(gè)0~9之間的數(shù)字
var str = 'iceman2016learn2017javascript2018';
console.log(reg.exec(str)); // ["2016", index: 6, input: "iceman2016learn2017javascript2018"]
看到這段代碼的時(shí)候不知道您有沒有一些疑惑,正則的內(nèi)容是/\d+/脂新,是匹配1到多個(gè)數(shù)字挪捕,2015是符合正則的,那么2也是符合正則的啊争便,為什么默認(rèn)就捕獲了2015呢级零? 這就是正則的貪婪性。
如何解決正則的貪婪性:在量詞元字符后面添加一個(gè)"?"即可滞乙。
var reg = /\d+?/g; // 出現(xiàn)一到多個(gè)0~9之間的數(shù)字
var str = 'iceman2016learn2017javascript2018';
console.log(reg.exec(str)); // ["2", index: 6, input: "iceman2016learn2017javascript2018"]
var ary = [] , res = reg.exec(str);
while (res) {
ary.push(res[0]);
res = reg.exec(str)
}
console.log(ary); // ["0", "1", "6", "2", "0", "1", "7", "2", "0", "1", "8"]
"?"在正則中的作用:
放在一個(gè)普通的元字符后面奏纪,代表出現(xiàn)0~1次;
放在一個(gè)量詞的元字符后面斩启,取消捕獲時(shí)候的貪婪性序调;
5.3、字符串中的match方法
match方法的作用是兔簇,把所有和正則匹配的字符都獲取到发绢。
var reg = /\d+?/g;
var str = 'zhufeng2015peixun2016dasgdas2017';
var ary = str.match(reg);
console.log(ary); // ["2", "0", "1", "5", "2", "0", "1", "6", "2", "0", "1", "7"]
注意:雖然在當(dāng)前的情況下,match比exec更加的簡潔一些垄琐,但是match存在一些自己處理不了的問題:在分組捕獲的情況下朴摊,match只能捕獲到大正則,而對于小正則捕獲的內(nèi)容是無法獲取的此虑。
六甚纲、分組捕獲
6.1、正則分組
正則分組的兩個(gè)作用:
改變優(yōu)先級(在“三朦前、元字符的應(yīng)用”中已經(jīng)有介紹到)
分組引用
\2代表和第二個(gè)分組出現(xiàn)一模一樣(和對應(yīng)的分組中的內(nèi)容和值都要一樣)的內(nèi)容介杆,\1代表和第一個(gè)分組出現(xiàn)一模一樣的內(nèi)容;
var reg = /^(\w)(\w)\1\2$/;
console.log(reg.test("icic")); // true
console.log(reg.test("r0g_")); // false
6.2韭寸、分組捕獲
正則在捕獲的時(shí)候春哨,不僅僅把大正則匹配的內(nèi)容捕獲到,而且還可以把小分組匹配的內(nèi)容捕獲到恩伺。
身份證中的數(shù)字都有意義的赴背,比如開頭的兩位代表省,中間的四位代表...所以對于一個(gè)身份中晶渠,有必要對其中的數(shù)字按照其意義進(jìn)行分組捕獲凰荚。
var reg = /^(\d{2})(\d{4})(\d{4})(\d{2})(\d{2})(?:\d{2})(\d)(?:\d|X)$/;
var str = "350324202904190216";
console.log(reg.exec(str));
注意:(?:) 在分組中?:的意思是只匹配不捕獲
輸出的內(nèi)容為:["350324200904190216", "35", "0324", "2009", "04", "19", "1", index: 0, input: "350324200904190216"]
其中:
- 350324200904190216 :大正則匹配的內(nèi)容
- 35 :第一個(gè)分組捕獲的內(nèi)容
- 0324 :第二個(gè)分組捕獲的內(nèi)容
- ……
在這里使用match方法的話,和exec獲取的內(nèi)容一樣:
console.log(str.match(reg));
再看一個(gè)例子:
var reg = /ice(\d+)/g;
var str = 'ice1234ice3456ice5678'
// 用exec執(zhí)行三次褒脯,每一次不僅僅把大正則匹配的獲取到便瑟,而且還可以獲取第一個(gè)分組匹配的內(nèi)容
console.log(reg.exec(str)); // ["ice1234", "1234", index: 0, input: "ice1234ice3456ice5678"]
console.log(reg.exec(str)); // ["ice3456", "3456", index: 7, input: "ice1234ice3456ice5678"]
console.log(reg.exec(str)); // ["ice5678", "5678", index: 14, input: "ice1234ice3456ice5678"]
// 而match只能捕獲大正則
console.log(str.match(reg)); // ["ice1234", "ice3456", "ice5678"]
此時(shí)match是只能捕獲大正則的內(nèi)容,所以match能做到的exec都能做到番川,match做不到的exec也能做到到涂。
總結(jié):只捕獲一次就好的脊框,那么用exec和match都可以,像本例中要連續(xù)捕獲三次的践啄,用match就捕獲不到小正則了浇雹。
七、replace基礎(chǔ)
var str = 'iceman2016iceman2017';
在上面定義的字符串中屿讽,現(xiàn)在需要將iceman替換成shoushou箫爷,我們知道字符串提供了一個(gè)replace方法,那么我們用replace來做一次:
str = str.replace('iceman','shoushou');
console.log(str); // shoushou2016iceman2017
從打印的結(jié)果可知聂儒,并沒有實(shí)現(xiàn)我們想要的效果,只替換了第一個(gè)iceman字符串硫痰,看MDN中對于replace方法的定義:
str.replace(regexp|substr, newSubStr|function)
replace() 方法返回一個(gè)由替換值替換一些或所有匹配的模式后的新字符串衩婚。模式可以是一個(gè)字符串或者一個(gè)正則表達(dá)式, 替換值可以是一個(gè)字符串或者一個(gè)每次匹配都要調(diào)用的函數(shù)。
由replace的定義可知效斑,匹配模式可以是一個(gè)正則的非春,所以我們用正則試一次:
str = str.replace(/iceman/g, 'shoushou');
console.log(str); // shoushou2016shoushou2017
在使用正則的時(shí)候已經(jīng)實(shí)現(xiàn)了需求,并且注意要使用修飾符g讓正則全局捕獲缓屠。
replace第一項(xiàng)的值是正則的情況下的實(shí)現(xiàn)原理:首先和exec捕獲一樣奇昙,把所有和正則匹配的內(nèi)容都捕獲到,然后把捕獲的內(nèi)容替換成我們需要替換的新內(nèi)容敌完, 在這里就是按照/iceman/g 把str中所有可以匹配的都捕獲到储耐,然后替換成iceman。
再看replace函數(shù)的定義滨溉,第二個(gè)參數(shù)可以是一個(gè)函數(shù):
var str = 'iceman2016iceman2017';
str = str.replace(/iceman/g, function () {
// 第一次執(zhí)行匿名函數(shù)輸出arguments的結(jié)果:["iceman", 0, "iceman2016iceman2017"]
// 第二次執(zhí)行匿名函數(shù)輸出arguments的結(jié)果:["iceman", 10, "iceman2016iceman2017"]
console.log(arguments);
return 'shoushou';
});
console.log(str); // shoushou2016shoushou2017
從打印結(jié)果可知:
先按照正則指定的規(guī)則什湘,到字符串中把正則匹配的內(nèi)容捕獲到,然后在每一次捕獲到之后晦攒,都把捕獲的內(nèi)容替換成新的內(nèi)容闽撤;
匿名函數(shù)執(zhí)行多少次,取決于正則能在字符串中捕獲多少次脯颜,在這里正則捕獲兩次哟旗,所以匿名函數(shù)也執(zhí)行兩次;
每一次執(zhí)行匿名函數(shù)栋操,里面?zhèn)鬟f的參數(shù)值arguments和自己通過exec捕獲到的結(jié)果是一樣的(每一次執(zhí)行匿名函數(shù)闸餐,和單獨(dú)執(zhí)行exec捕獲的內(nèi)容一致);
小分組捕獲的內(nèi)容矾芙,在這里同樣可以獲取到(所以說绎巨,replace和exec原理是一模一樣的,比match要強(qiáng)大)
return的結(jié)果是什么蠕啄,就相當(dāng)于把當(dāng)前這一次大正則捕獲的內(nèi)容替換成返回的內(nèi)容场勤。如果不寫return戈锻,默認(rèn)使用undefined來進(jìn)行替換,如果不想實(shí)現(xiàn)替換的話和媳,我們可以把捕獲的內(nèi)容再返回回去
return arguments[0]
格遭;
var str = 'iceman2015iceman2016';
var reg = /iceman(\d+)/g;
str = str.replace(reg, function () {
// 第一次捕獲arguments的值:["iceman2015", "2015", 0, "iceman2015iceman2016"]
// 第二次捕獲arguments的值:["iceman2016", "2016", 10, "iceman2015iceman2016"]
console.log(arguments);
});
從打印中可知,當(dāng)有分組的時(shí)候留瞳,arguments的第二個(gè)參數(shù)開始分組的內(nèi)容拒迅,所以可以用arguments[1]這樣的方式來獲取分組的內(nèi)容。
八她倘、replace實(shí)戰(zhàn)
從上面的介紹中已經(jīng)知道璧微,正則的捕獲有三種方式:正則的exec方法、字符串的match方法硬梁、字符串的replace方法前硫。
其中replace是將原有的字符串替換成我們想要的新的字符串,在不適用正則的情況下荧止,執(zhí)行一次replace只能替換字符串中的一個(gè)屹电,而使用正則的話,可以一次批量的把所有的正則匹配的內(nèi)容都替換掉跃巡。
8.1危号、實(shí)戰(zhàn)一:小寫數(shù)字替換成大寫數(shù)字
var str = '今年是2017年'; //
var ary = ['零', '壹', '貳', '叁', '肆', '伍', '陸','柒', '捌', '玖', '拾'];
// 實(shí)現(xiàn)替換的話,需要捕獲到數(shù)字素邪,并且把數(shù)字當(dāng)作ary的索引獲取對應(yīng)的漢字進(jìn)行替換
str = str.replace(/\d/g, function () {
/*
* 第一次執(zhí)行:大正則捕獲的是2外莲,返回的是ary[2] --> '貳'
* 第二次執(zhí)行:大正則捕獲的是0,返回的是ary[0] --> '零'
* .....
*/
return ary[arguments[0]];
});
console.log(str);
8.2兔朦、實(shí)戰(zhàn)二:獲取一個(gè)字符串中出現(xiàn)次數(shù)最多的字符苍狰,并且獲取出現(xiàn)的次數(shù)
var str = 'zhongguofujianxiamensimingshoushou';
// 1)獲取每一個(gè)字符出現(xiàn)的次數(shù)
var obj = {};
str.replace(/[a-z]/gi, function () {
var val = arguments[0];
obj[val] >= 1 ? obj[val] += 1: obj[val] = 1;
});
// 2)獲取最多出現(xiàn)的次數(shù)
var maxNum = 0;
for (var key in obj) {
obj[key] > maxNum ? maxNum = obj[key] : null;
}
// 3)把所有符合出現(xiàn)maxNum次數(shù)的都獲取到
var ary = [];
for (var key in obj) {
obj[key] === maxNum ? ary.push(key) : null;
}
console.log('整個(gè)字符串中出現(xiàn)次數(shù)最多的字符是:' + ary.toString() + '出現(xiàn)了' + maxNum + '次');
8.3、實(shí)戰(zhàn)三:模板引擎實(shí)現(xiàn)的初步原理
var str = 'my name is {0}, my age is {1}, i come from {2}, i love {3} ~~~';
var ary = ['iceman', '26', 'China', 'javascript']
str = str.replace(/{(\d+)}/g , function () {
return ary[arguments[1]];
});
console.log(str);