9.19--9.23
第7章 正則表達(dá)式
正則表達(dá)式是一個(gè)拆分字符串并查詢相關(guān)信息的過程。
推薦練習(xí)網(wǎng)站:
js Bin ? jsbin.com
www.regexplanet.com/advanced/javascript/index.html
7.1為什么正則表達(dá)式很牛
示例7.1 在字符串中測試特殊模式
//99999-9999驗(yàn)證是否符合這個(gè)格式
function?isThisAZipCode(candidate){
if(typeof?candidate?!==?'string'?||?candidate.length?!=?10)?return?false;
for(var?n=0;?n
var?c?=?candidate[n];
switch(n){
case?0:?case?1:?case?2:?case?3:?case?4:
case?6:?case?7:?case?8:?case?9:
if(c<'0'?||?c?>'9')?return?false;
break;
case?5:
if(c?!=?'-')?return?false;
break;
}
}
return?true;
}
console.log(isThisAZipCode('12345-1234'))
console.log(isThisAZipCode('1245-1234'))
function?isThisAZipCode1(candidate){
return?/^\d{5}-\d{4}$/.test(candidate)
}
console.log(isThisAZipCode1('12345-1234'))
console.log(isThisAZipCode1('1245-1234'))
同樣的字符串驗(yàn)證,用正則表達(dá)式看起來更簡潔、更優(yōu)雅。
7.2 正則表達(dá)式進(jìn)階
7.2.1 正則表達(dá)式解釋
正則表達(dá)式通常被稱為一個(gè)模式,是一個(gè)用簡單方式描述或者匹配一系列符合某個(gè)句法規(guī)則的字符串。表達(dá)式本身包含了允許定義這些模式的術(shù)語和操作符。
在js中,有兩種方法可以創(chuàng)建正則表達(dá)式:通過正則表達(dá)式字面量,或者通過構(gòu)造RegExp對象的實(shí)例。
var pattern = /test/;
正則字面量是用正斜杠進(jìn)行界定的。
var pattern = new RegExp('test');
構(gòu)造一個(gè)RegExp實(shí)例,將正則作為字符串傳入。
這兩種格式在pattern變量中創(chuàng)建的正則表達(dá)式都是一樣的。
在開發(fā)過程中,如果正則是已知的,則優(yōu)先選擇字面量語法,而構(gòu)造器方式則是用于在運(yùn)行時(shí),通過動(dòng)態(tài)構(gòu)建字符串來構(gòu)建正則表達(dá)式。
三個(gè)標(biāo)志:
i——讓正則表達(dá)式不區(qū)分大小寫,所以/test/i不僅可以匹配'text',還可以匹配‘TEST',‘Test'等
g——匹配模式中的所有實(shí)例,而不是默認(rèn)只匹配第一次出現(xiàn)的結(jié)果。
m——允許匹配多個(gè)行,比如可以匹配textarea中的值。
這些標(biāo)志將附加到字面量尾部(例如/test/ig)或者作為RegExp構(gòu)造器的第二個(gè)字符串參數(shù)(new RegExp('test','ig'))
7.2.2 術(shù)語與操作符
正則表達(dá)式,由術(shù)語和驗(yàn)證這些術(shù)語的操作符組成。
精確匹配
如果一個(gè)字符不是特殊字符或操作符,則表示該字符必須在表達(dá)式中出現(xiàn)。例如,在/test/正則中,有4個(gè)術(shù)語,它們表示這些字符必須在一個(gè)字符串中出現(xiàn),才能匹配該模式。
匹配一類字符
可以通過將字符集放到中括號內(nèi),來指定該字符集操作符:[abc]。
[abc],是說我們要匹配'a','b','c'中的任何一個(gè)字符。它只能匹配候選字符串中的一個(gè)字符。
[^abc],匹配除了'a','b','c'以外的任意字符。
[a-m] = [abcdefghijklm],匹配'a'到'm'之間的任何一個(gè)小寫字母
轉(zhuǎn)義
在正則里,使用反斜杠可以對任意字符進(jìn)行轉(zhuǎn)義,讓被轉(zhuǎn)義字符作為字符本身進(jìn)行匹配。 例如:\[ ?匹配 [ , ?\\匹配 \。
匹配開始與匹配結(jié)束
插入符號(^),如果作為正則表達(dá)式的第一個(gè)字符,則表示要從字符串的開頭進(jìn)行匹配。例如/^test/ 只匹配以'test'開頭的字符串。
美元符號($),表示該模式必須出現(xiàn)在字符串的結(jié)尾。例如 /test$/ 只匹配以'test'結(jié)尾的字符串。
同時(shí)使用^和$則表明指定的模式必須包含整個(gè)候選字符串: /^test$/
重復(fù)出現(xiàn)
. 在一個(gè)字符后面加一個(gè)問號(?),可以定義為該字符是可選的(也就是,可以出現(xiàn)一次或根本不出現(xiàn))。例如/t?est/可以匹配'test'和'est'。
. 如果一個(gè)字符要出現(xiàn)一次或多次,可以使用加號(+)。例如/t+est/可以匹配/'test'/,/'ttest'/,/'tttest'/,而不能匹配/'est'/。
. 如果一個(gè)字符要出現(xiàn)零次或多次,可以使用星號(*)。例如/t*est/可以匹配/'test'/,/'ttest'/,/'tttest'/,以及/'est'/。
. 也可以在字符后面的花括號里指定一個(gè)數(shù)字來表示重復(fù)次數(shù)。例如,/a{4}/表示匹配含有連續(xù)四個(gè)'a'字符的字符串。
. 也可以在字符后面的花括號里指定兩個(gè)數(shù)字(用逗號隔開)來表示重復(fù)次數(shù)區(qū)間。例如,/a{4,10}/表示匹配任何含有連續(xù)4個(gè)或10個(gè)'a'字符的字符串。
. 次數(shù)區(qū)間的第二個(gè)值是可選的(但是要保留逗號),其表示一個(gè)開區(qū)間。例如,/a{4,}/表示匹配任何含有連續(xù)4個(gè)或多于4個(gè)'a'字符的字符串。
這些重復(fù)操作符可以是貪婪的或非貪婪的。默認(rèn)情況下,它們是貪婪的:它們匹配所有的字符組合。在操作符后面加一個(gè)問號?字符,如a+?,可以讓該表達(dá)式編程成為非貪婪的:進(jìn)行最小限度的匹配。
例如:如果我們對字符串'aaa'進(jìn)行匹配,/a+/將匹配所有這三個(gè)字符,而非貪婪表達(dá)式/a+?/則只匹配一個(gè)a字符。
預(yù)定義字符類
有一些我們想匹配的字符,是不可能用字面量字符來表示的(如回車),還有一些我們可能經(jīng)常想匹配的字符類(如小數(shù)位數(shù)或一組空白字符),正則表達(dá)式語法提供了很多表示這些字符或常用類的預(yù)定義術(shù)語。
\t ? ?匹配 ? ?水平制表符
\b匹配 ? 空格
\v匹配 ? 垂直制表符
\f匹配 ? 換頁符
\r匹配 ? 回車
\n匹配 ?換行符
\cA: \cZ匹配 ? 控制符,例如\cM 匹配一個(gè) Control-M
\x0000: \xFFFF匹配 ? ?十六進(jìn)制Unicode碼
\x00: \xFF匹配 ? ?十六進(jìn)制ASCII碼
.匹配 ? ?除了換行(\n)之外的任意字符
\d匹配 ? ?任意數(shù)字,等價(jià)于[0-9]
\D匹配 ? 任意非數(shù)字,等價(jià)于[^0-9]
\w匹配 ? 包括下劃線的任意單詞字符,等價(jià)于[A-Za-z0-9_]
\W匹配 ? ?任何非單詞字符,等價(jià)于[^A-Za-z0-9_]
\s匹配 ? ?任何空白字符,包括空格、制表符、換頁符等
\S匹配 ? ?任何非空白字符
\b匹配 ? ?單詞邊界
\B匹配 ? ?非單詞邊界
分組
如果將操作符應(yīng)用于一組術(shù)語,可以在該組上使用小括號。例如/(ab)+/匹配一個(gè)或多個(gè)連續(xù)出現(xiàn)的子字符串'ab'
當(dāng)正則表達(dá)式有一部分是用括號進(jìn)行分組時(shí),它具有雙重責(zé)任,同時(shí)也創(chuàng)建所謂的捕獲。
或操作符(OR)
可以用豎線(|)字符表示或者的關(guān)系。例如:/a|b/匹配'a'或'b'字符, /(ab)+|(cd)+/匹配出現(xiàn)一次或多次的'ab'或'cd'。
反向引用
正則表達(dá)式中最復(fù)雜的術(shù)語是,在正則中所定義的捕獲的反向引用。
在反斜杠后面加一個(gè)要引用的捕獲數(shù)量,該數(shù)字從1開始,如\1,\2等。
例如:/^([dtn])a\1/可以任意一個(gè)以'd','t','n'開頭,且后面跟著一個(gè)'a'字符,并且再后面跟著的是和第一個(gè)捕獲相同字符的字符串。\1匹配的字符需要在執(zhí)行的時(shí)候才能確定。
它在匹配HTML標(biāo)記的時(shí)候非常有用 例如 /<(\w+)>(.+)<\/\1>/
要匹配像'whatever'這樣的元素,不使用反向引用,是無法做到的,因?yàn)槲覀儫o法知道關(guān)閉標(biāo)簽和開始標(biāo)簽是否匹配。
7.3 編譯正則表達(dá)式
正則表達(dá)式的兩個(gè)重要階段是編譯和執(zhí)行。編譯發(fā)生在正則表達(dá)式第一次被創(chuàng)建的時(shí)候,而執(zhí)行則是發(fā)生在我們使用編譯過的正則表達(dá)式進(jìn)行字符串匹配的時(shí)候。
在編譯期間,表達(dá)式通過js引擎進(jìn)行解析,并轉(zhuǎn)換成其內(nèi)部表示。解析和轉(zhuǎn)換這個(gè)過程,在每個(gè)正則表達(dá)式創(chuàng)建的時(shí)候都會(huì)發(fā)生。
通過對稍后要用的正則表達(dá)式進(jìn)行預(yù)定義(因此也預(yù)編譯),可以獲得一些明顯的速度提升。
在js中,有兩種方式可以創(chuàng)建編譯后的正則表達(dá)式:通過字面量方式,或通過構(gòu)造器方式。
示例7.2 創(chuàng)建編譯后正則表達(dá)式的兩種方式
var?re1?=?/test/i;
var?re2?=?new?RegExp('test','i');
assert(re1.toString()?==?'/test/i','Verify?the?contents?of?the?expression.');
assert(re1.test('TesT'),'YES,?it\'s?case-insensitive.');
assert(re2.test('TesT'),'This?one?is?too.');
assert(re1.toString()?==?re2.toString(),'The?regular?expressions?are?equal.');
assert(re1?!=?re2,?'But?they?are?different?objects.')
正則表達(dá)式只編譯一次,并將其保存在一個(gè)變量中供后續(xù)使用,這是一個(gè)重要的優(yōu)化過程。
每個(gè)正則表達(dá)式都有一個(gè)獨(dú)立的對象表示:每次創(chuàng)建正則表達(dá)式,都會(huì)為此創(chuàng)建一個(gè)新的正則表達(dá)式對象。
用構(gòu)造器創(chuàng)建正則表達(dá)式的使用,可以在運(yùn)行時(shí)通過動(dòng)態(tài)創(chuàng)建的字符串構(gòu)建和編譯一個(gè)正則表達(dá)式。對于構(gòu)建大量重用的復(fù)雜表達(dá)式來說,這是非常有用的。
示例 7.3 編譯一個(gè)稍后使用的運(yùn)行時(shí)正則表達(dá)式
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
function?findClassInElements(className,type){
var?elems?=?document.getElementsByTagName(type?||?'*');
var?regex?=?new?RegExp('(^|\\s)'?+?className?+?'(\\s|$)');
var?results?=?[];
for(var?i=0,length?=?elems.length;?i
if(regex.test(elems[i].className)){
results.push(elems[i])
}
}
return?results;
}
assert(findClassInElements('ninja','div').length?==?2,'The?right?amount?of?div?ninjas?was?found.');
assert(findClassInElements('ninja','span').length?==?1,'The?right?amount?of?span?ninjas?was?found.');
assert(findClassInElements('ninja').length?==?3,'The?right?amount?of?ninjas?was?found.');
注意,基于傳遞給函數(shù)的樣式名稱,使用new RegExp()構(gòu)造器來編譯正則表達(dá)式,這是一個(gè)我們無法用正則字面量實(shí)現(xiàn)的實(shí)例,因?yàn)椴恢酪阉鞯臉邮矫Q是什么。
該正則表達(dá)式匹配的字符串要以字符串或空格開始,面后跟著指定樣式名稱,并且緊隨其后的是一個(gè)空白字符或結(jié)束字符串。要注意雙反斜杠的使用:\\s。創(chuàng)建帶有反斜杠的字面量正則表達(dá)式時(shí),只需要提供一個(gè)反斜杠即可。但是,由于我們在字符串中寫反斜杠,所以需要雙反斜杠進(jìn)行轉(zhuǎn)義。
一旦正則表達(dá)式被編譯了,就可以利用該表達(dá)式的test()方法收集匹配的元素。
7.4 捕獲匹配的片段
正則表達(dá)式的實(shí)用性表現(xiàn)在捕獲已匹配的結(jié)果上,這樣我們便可以在其中進(jìn)行處理。
7.4.1 執(zhí)行簡單的捕獲
示例7.4 捕獲嵌入值的簡單函數(shù)
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
function?getOpacity(elem){
var?filter?=?elem.style.filter;
return?filter???filter.indexOf('opacity=')?>=?0???(parseFloat(filter.match(/opacity=([^)]+/)[1])/100)+'':'':elem.style.opacity;
}
assert(getOpacity(document.getElementById('opacity'))?==?'0.5','The?opacity?of?the?element?has?been?obtained.')
match返回的數(shù)組的第一個(gè)索引的值總是該匹配的完整結(jié)果,然后是每個(gè)后續(xù)捕獲結(jié)果。
因此,第0個(gè)索引的值將是完整的匹配值filter:alpha(opacity=50),下一個(gè)匹配則是50.
捕獲是由正則表達(dá)式中的小括號所定義。
利用String對象的match()方法,使用局部正則表達(dá)式(沒有全局標(biāo)記)會(huì)返回一個(gè)數(shù)組,該數(shù)組包含了在匹配操作中成功匹配的整個(gè)字符串以及其他捕獲結(jié)果。
7.4.2 用全局表達(dá)式進(jìn)行匹配
當(dāng)應(yīng)用全局正則表達(dá)式(添加一個(gè)g標(biāo)記)時(shí),返回值依然是一個(gè)數(shù)組,返回的數(shù)組包含了全局匹配結(jié)果。在這種情況下,每個(gè)匹配的捕獲結(jié)果是不會(huì)返回的。
示例7.5 使用match()進(jìn)行全局搜索和局部搜索時(shí)的不同
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
var?html?=?'Hello?world!';
var?results?=?html.match(/<(\/?)(\w+)([^>]*?)>/);
assert(results[0]?==?'','The?entire?match.');
assert(results[1]?==?'','The?(missing)?slash.')
assert(results[2]?==?'div','The?tag?name.');
assert(results[3]?==?'?class="test"','The?attributes.');
var?all?=?html.match(/<(\/?)(\w+)([^>]*?)>/g);
assert(all[0]?==?'','Opening?div?tag.');
assert(all[1]?==?'','Opening?b?tag.')
assert(all[2]?==?'','Closing?b?tag.')
assert(all[3]?==?'','Opening?i?tag')
assert(all[4]?==?'','Closing?i?tag')
assert(all[5]?==?'','Closing?div?tag.')
在進(jìn)行局部正則匹配時(shí),只有一個(gè)實(shí)例被匹配了,并且該匹配的捕獲結(jié)果也返回來了;但是在進(jìn)行全局正則匹配時(shí),返回的卻是匹配結(jié)果的列表。
如果捕獲對我們來說很重要,我們可以使用正則表達(dá)式的exec()方法,在全局正則匹配時(shí)恢復(fù)捕獲功能。該方法可以對一個(gè)正則表達(dá)式進(jìn)行多次調(diào)用,每次調(diào)用都可以返回下一個(gè)匹配的結(jié)果。
示例7.6 使用exec()方法進(jìn)行捕獲和全局搜索
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
var?html?=?'Hello?world!';
var?tag?=?/<(\/?)(\w+)([^>]*?)>/g,match;
var?num?=?0;
while((match?=?tag.exec(html))?!==?null){
assert(match.length?==?4,'Every?match?finds?each?tag?and?3?captures.');
num++
}
assert(num==6,'3?opening?and?3?closing?tags?found.')
在本例中,反復(fù)調(diào)用了exec()方法,該方法保存了上一次調(diào)用的狀態(tài),這樣每個(gè)后續(xù)調(diào)用就可以繼續(xù)下去了,直到全局匹配。每個(gè)調(diào)用返回的都是下一個(gè)匹配及其捕獲內(nèi)容。
通過使用match()和exec(),我們總是可以找到想要尋找的精確匹配(及捕捉)。
7.4.3 捕獲的引用
有兩種方法,可以引用捕獲到的匹配結(jié)果:一個(gè)是自身匹配,一個(gè)是替換字符串。
示例7.7 使用反向引用匹配HTML標(biāo)簽內(nèi)容
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
var?html?=?'Hello?world!';
var?pattern?=?/<(\w+)([^>]*?)>(.*?)<\/\1>/g;
var?match?=?pattern.exec(html);
assert(match[0]?==?'Hello','The?entire?tag,?start?to?finish.')
assert(match[1]?==?'b','The?tag?name');
assert(match[2]?==?'?class="hello"','The?tag?attributes.');
assert(match[3]?==?'Hello','The?contents?of?the?tag.')
match?=?pattern.exec(html);
assert(match[0]?==?'world!','The?entire?tag,?start?to?finish.');
assert(match[1]?==?'i','The?tag?name');
assert(match[2]?==?'','The?tag?attributes.')
assert(match[3]?==?'world!','The?contents?of?the?tag.')
我們使用\1引用了表達(dá)式中的第一個(gè)捕獲,在本例中,該捕獲是標(biāo)簽名稱。使用這些信息,我們可以匹配相應(yīng)的結(jié)束標(biāo)簽,反向引用到匹配的捕獲結(jié)果。(如果當(dāng)前標(biāo)簽有嵌入同名標(biāo)簽,還要再考慮)
還有一個(gè)方法可以獲得捕獲的引用,就是通過調(diào)用replace()方法替換字符串的時(shí)候。
示例:
assert('fontFamily'.replace(/([A-Z])/g,'-$1').toLowerCase()?==?'font-family','Convert?the?camelCase?into?dashed?notation.')
首先獲取的捕獲值,在替換字符串中進(jìn)行了引用(通過$1)。這種方式允許我們指定一個(gè)替換字符串,即使是在運(yùn)行之前還不知道它的值。
7.4.4 沒有捕獲的分組
小括號有雙重責(zé)任:不僅要進(jìn)行分組操作,還可以指定捕獲。如果正則表達(dá)式中有大量的分組,就會(huì)引起很多不必要的捕捉。
要讓一組括號不進(jìn)行結(jié)果捕獲,正則表達(dá)式的語法允許我們在開始括號后加一個(gè)?:標(biāo)記,這就是所謂的被動(dòng)子表達(dá)式。
var pattern = /((?:ninja-)+)sword/;
該表達(dá)式只會(huì)為外層的括號創(chuàng)建捕獲。內(nèi)層括號被轉(zhuǎn)換為一個(gè)被動(dòng)子表達(dá)式。
示例7.8 不帶捕獲的分組
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
var?pattern?=?/((?:ninja-)+)sword/;
var?ninjas?=?'ninja-ninja-sword'.match(pattern);
assert(ninjas.length?==?2,'Only?one?capture?was?returned.')
assert(ninjas[1]?==?'ninja-ninja-','Matched?both?words,?without?any?extra?capture.')
通過示例,可以看到,被動(dòng)子表達(dá)式可以阻止不必要的捕獲。
在不需要捕獲的時(shí)候,我們都應(yīng)該盡可能地使用非捕獲(被動(dòng))分組,以便讓表達(dá)式引擎在記憶和返回捕獲工作上做更多的工作。
7.5 利用函數(shù)進(jìn)行替換
String對象的replace()方法是一個(gè)強(qiáng)大且靈活的方法,將正則表達(dá)式作為replace()方法的第一個(gè)參數(shù)時(shí),導(dǎo)致在該模式的匹配元素上進(jìn)行替換,而不是在固定字符串上進(jìn)行替換。
例如:'ABCDEfg'.replace(/[A-Z]/g,'x') 可以讓所有的大寫字符都替換成'X',結(jié)果為XXXXXfg
replace()最強(qiáng)大的特性是可以接受一個(gè)函數(shù)作為替換值,而不是一個(gè)固定的字符串。
當(dāng)替換值(第二個(gè)參數(shù))是一個(gè)函數(shù)時(shí),每個(gè)匹配都會(huì)調(diào)用該函數(shù)(全局搜索會(huì)在源字符串中匹配所有的模式實(shí)例)并帶有一串參數(shù)列表。
.匹配的完整文本
.匹配的捕獲,一個(gè)捕獲對應(yīng)一個(gè)參數(shù)。
.匹配字符在源字符串中的索引
.源字符串
示例7.9 將中橫線字符串轉(zhuǎn)換成駝峰拼寫法
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
function?upper(all,letter){return?letter.toUpperCase();}
assert('border-bottom-width'.replace(/-(\w)/g,upper)?==?'borderBottomWidth','Camel?cased?a?hyphenated?string.')
函數(shù)在每次被調(diào)用的時(shí)候,傳入完整的字符串作為第一個(gè)參數(shù),捕獲結(jié)果作為第二個(gè)參數(shù)。
示例7.10 壓縮查詢字符串的技術(shù)
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
function?compress(source){
var?keys?=?{};
source.replace(/([^=&]+)=([^&]*)/g,function(full,key,value){
keys[key]?=?(keys[key]???keys[key]?+?','?:?'')?+?value;
return?'';
})
var?result?=?[];
for(var?key?in?keys){
result.push(key?+?'='?+?keys[key]);
}
return?result.join('&')
}
assert(compress('foo=1&foo=2&blah=a&blah=b&foo=3')?==?'foo=1,2,3&blah=a,b','Compression?is?OK!')
以上示例最為趣的一點(diǎn)是如何使用字符串的replace()方法來遍歷一個(gè)字符串,而不是一個(gè)實(shí)際的搜索替換機(jī)制。其關(guān)鍵點(diǎn)有兩個(gè):傳遞一個(gè)函數(shù)作為替換值參數(shù),該函數(shù)并不是返回實(shí)際的值,而是簡單地利用它作 一種搜索手段。
示例代碼首先聲明一個(gè)哈希,用于保存在源查詢字符串中找到的鍵值對。然后在源字符串上調(diào)用replace()方法,傳入匹配鍵值對的正則,并捕獲匹配的鍵和值。我們還傳入了一個(gè)函數(shù),該函數(shù)將接收完整匹配值、捕獲的鍵、捕獲的值作為參數(shù)。這些捕獲的值將保存在哈希中,以供稍后進(jìn)行引用。
在replace()返回后,我們聲明一個(gè)數(shù)組,然后遍歷查找到的keys,并且每個(gè)結(jié)果都聚合到該數(shù)組中。最后使用&分隔符,將數(shù)組中的所有結(jié)果都合并成一個(gè)字符串,然后返回該字符串。
我們可以使用String對象的replace()方法作為字符串搜索機(jī)制。搜索結(jié)果不僅快速,而且簡單、有效。
7.6 利用正則表達(dá)式解決常見問題
7.6.1 修剪字符串
示例7.11 從字符串中刪除空格的常見解決方案
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
function?trim(str){
return?(str?||?'').replace(/^\s+|\s+$/g,'');
}
assert(trim('?#id?div.class??')?==?'#id?div.class','Extra?whitespace?trimmed?from?a?selector?string.')
只是調(diào)用了一次replace()方法,并傳入一個(gè)匹配字符串開頭和結(jié)尾空格的正則來完成這項(xiàng)工作。
另外兩種方法
示例7.12 雙重替換的修剪實(shí)現(xiàn)方式
function?trim(str){
return?str.replace(/^\s\s*/,'').replace(/\s\s*$/,'');
}
執(zhí)行兩次替換:一個(gè)是開頭的空格,另一個(gè)是結(jié)尾的空格。
示例7.13 使用字符串的slice方法剔除字符串尾部空格的方式
function?trim(str){
var?str?=?str.replace(/^\s\s*/,'')?,ws?=?/\s/,?i?=?str.length;
while(ws.test(str.charAt(--i)));
return?str.slice(0,i+1);
}
使用一個(gè)正則表達(dá)式剔除字符串開頭的空格,并使用slice操作剔除字符串尾部的空格。
三種trim()實(shí)現(xiàn)的性能比較
短字符串 ? ? ? 文檔
示例7.11 ? ? ? ?8.7ms ? ? ?2075.8ms
示例7.128.5ms ? ? ?3706.7ms
示例7.1313.8ms ? ?169.4ms
大多數(shù)js庫使用了第一種解決方案
7.6.2 匹配換行符
示例7.14 匹配所有的字符,包括換行符
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
var?html?=?'Hello\nworld!';
assert(/.*/.exec(html)[0]?===?'Hello','A?normal?capture?doesn\'t?handle?endlines.')
assert(/[\S\s]*/.exec(html)[0]?==='Hello\nworld!','Matching?everything?with?a?character?set.')
assert(/(?:.|\s)*/.exec(html)[0]?===?'Hello\nworld!','Using?a?non-capturing?group?to?match?everything.')
根據(jù)代碼的簡單性,/[\S\s]*/提供的解決方案通常被認(rèn)為是最佳方案。。
7.6.3 Unicode
示例7.15 匹配 Unicode字符
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
var?text?=?'\u5FCD\u8005\u30D1\u30EF\u30FC';
var?matchAll?=?/[\w\u0080-\uFFFF_-]+/;
assert((text).match(matchAll),'Our?regexp?matches?unicode!')
通過創(chuàng)建一個(gè)包含\w的字符類,可以將字符匹配范圍擴(kuò)展到整個(gè)Unicode字符集,再加上一套字符代碼在128(十六進(jìn)制為0x80)以上的字符,從而匹配所有的“正常”字符。從128開始,不僅可以匹配所有的Unicode字符,也可以匹配ASCII字符。
通過在\u0080上添加整個(gè)Unicode字符集,我們不僅可以匹配字母字符,還可以匹配到所有的Unicode標(biāo)點(diǎn)符號, 以及其他特殊字符。
7.6.4 轉(zhuǎn)義字符
示例7.16 在CSS選擇器中匹配轉(zhuǎn)義字符
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
var?pattern?=?/^((\w+)|(\\.))+$/;??//該正則表達(dá)式允許匹配一個(gè)單詞字符,或一個(gè)反斜杠及后面跟隨任意字符,或者兩者都可以匹配
var?tests?=?['formUpdate','form\\.update\\.whatever','form\\:update','\\f\\o\\r\\m\\u\\p\\d\\a\\t\\e','form:update'];
for(var?n=0;?n
assert(pattern.test(tests[n]),tests[n]+'?is?a?valid?identifire.')
}
最后一個(gè)不能通過,其它都能通過。
這個(gè)特殊表達(dá)式允許匹配一個(gè)單詞字符序列,或在一個(gè)反斜杠后面跟隨任何字符的序列。