30天學(xué)習(xí)計(jì)劃 js忍者秘籍 第7章 正則表達(dá)式

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è)反斜杠后面跟隨任何字符的序列。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,016評論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內(nèi)容