JS 正則表達式是 JS 學習過程當中的一大難點,繁雜的匹配模式足以讓人頭大,不過其複雜性和其學習難度也賦予了它強大的功能。文章從 JS 正則表達式的正向前瞻提及,實現否認匹配的案例。本文適合有必定 JS 正則表達式基礎的同窗,若是對正則表達式並不瞭解,還需先學習基礎再來觀摩這門否認大法。html
不知道你們在寫JS有沒有遇到過這樣的狀況,當你要處理一串字符串時,須要寫一個正則表達式來匹配當中不是 XXX 的文本內容。聽起來好像略有些奇怪,匹配不是 XXX 的內容,不是 XXX 我匹配它幹嗎啊,我要啥匹配啥不就完了。你還別說,這個玩意還真的有用,無論你遇沒遇到過,反正我是遇到了。具體的需求例如:當你收到一串HTML代碼,須要對這一串HTML代碼過濾,將裏面全部的非<p>標籤都改成<p>。這裏確定有很多同窗就要嫌棄了,「將全部標籤都改成<p>,那就把任意標籤都改成<p>不就完了?」,因而乎一行代碼拍腦殼而生:web
1 var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>'; 2 var reg = /<(\/?).*?>/g; 3 var newStr = str.replace(reg, "<$1p>"); 4 console.log(newStr);//<p>,<p>,<p>,<p>,</p>,</p>,</p>,</p>
注意這個方法中有一個引用符 「$1」 ,這個的意思引用正則的表達式的第1個分組,能夠用$N來表示在正則表達式中的第N個捕獲的引用。就那上面的例子來講,"(\/?)"這個一個表達式的含義是,"\/"這個字符出現0次或者1次,而$1這個引用呢就至關於和「\/」這個字符門當戶對的大閨女,她已下定決心今生非"\/"不嫁。因此當匹配到有一個「\/」的時候,$1這個引用就把它捕獲下來,從如今起,你的就是個人,個人就是你的啦,所以$1等價於"(\/?)"所匹配到的字符;反之若是沒有匹配到"\/"這個字符,那$1這個引用就得空守閨房,獨立熬過一個又一個漫長的夜晚,由於它心裏極度的空虛,因此$1就等價於""(也就是空串)。正則表達式
這裏先聊了聊引用和捕獲的概念,由於後面還會用到它。那麼話說回來,剛纔那一串正則,不是已經完美的實現了需求了嗎?還研究什麼否認匹配啊?各位看官別急,且聽小生慢慢道來。咱們都知道,需求這個東西,確定是會改嘀(◐ˍ◑)。如今改一改需求:當你收到一串HTML代碼,須要對這一串HTML代碼過濾,將裏面全部的非<p>或者<div>標籤都改成<p>。WTF?這算哪門子需求?話說我當時也是這種反應。咱們如今分析一下這個需求到底要幹嗎,也就是說,保留原HTML代碼中的<p>和<div>,將其餘標籤統一修改成<p>。咦...這下可很差弄了,剛纔那串代碼看上去貌似行不通了。因此說這時候就只能用排除法了,排除掉<p>和<div>,替換掉其餘的標籤。那麼問題也就來了,如何排除?express
在正則表達式當中有個東西叫作前瞻,有的管它叫零寬斷言:瀏覽器
表達式 | 名稱 | 描述 |
(?=exp) | 正向前瞻 | 匹配後面知足表達式exp的位置 |
(?!exp) | 負向前瞻 | 匹配後面不知足表達式exp的位置 |
(?<=exp) | 正向後瞻 | 匹配前面知足表達式exp的位置(JS不支持) |
(?<!exp) | 負向後瞻 | 匹配前面不知足表達式exp的位置(JS不支持) |
因爲 JS 原生不支持後瞻,因此這裏就不研究它了。咱們來看看前瞻的做用:學習
1 var str = 'Hello, Hi, I am Hilary.'; 2 var reg = /H(?=i)/g; 3 var newStr = str.replace(reg, "T"); 4 console.log(newStr);//Hello, Ti, I am Tilary.
在這個DEMO中咱們能夠看出正向前瞻的做用,一樣是字符"H",可是隻匹配"H"後面緊跟"i"的"H"。就至關於有一家公司reg,這時候有多名"H"人員前來應聘,可是reg公司提出了一個硬條件是必須掌握"i"這項技能,因此"Hello"就天然的被淘汰掉了。spa
那麼負向前瞻呢?道理是相同的:code
1 var str = 'Hello, Hi, I am Hilary.'; 2 var reg = /H(?!i)/g; 3 var newStr = str.replace(reg, "T"); 4 console.log(newStr);//Tello, Hi, I am Hilary.
在這個DEMO中,咱們把以前的正向前瞻換成了負向前瞻。這個正則的意思就是,匹配"H",且後面不能跟着一個"i"。這時候"Hello"就能夠成功的應聘了,由於reg公司修改了他們的招聘條件,他們說"i"這門技術會有損公司的企業文化,因此咱們不要了。htm
說到這裏,讓咱們回到最初的那個需求,讓咱們先用負向前瞻來實現第一個需求:將全部非<p>標籤替換爲<p>。話說同窗們剛學完了負向前瞻,瞭解到了JS的博大精深,心中暗生竊喜,提筆一揮:blog
1 var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>'; 2 var reg = /<(\/?)(?!p)>/g; 3 var newStr = str.replace(reg, "<$1p>"); 4 console.log(newStr);//<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>
What?爲何不起做用呢?說好的否認大法呢?這裏就得聊一聊前瞻的一個特性,前瞻是非捕獲性分組,什麼玩意是非捕獲性分組呢?還記得前面那位非"\/"不嫁的大閨女$1嗎,人家爲何那麼一往情深,是由於她早已將"\/"的心捕獲了起來,而前瞻倒是非捕獲性分組,也就是你捕獲不到人家。也就是說沒法經過引用符"\n"或者"$n"來對其引用:
1 var str = 'Hello, Hi, I am Hilary.'; 2 var reg = /H(?!i)/g; 3 var newStr = str.replace(reg, "T$1"); 4 console.log(newStr);//T$1ello, Hi, I am Hilary.
注意其中輸出的語句,前面咱們能夠看到,若是引用符沒有匹配到指定的字符,那麼就會顯示空串"",但是這裏是直接顯示了整個引用符"$1"。這是由於前瞻表達式根本就沒有捕獲,沒有捕獲也就沒有引用。
非捕獲性是前瞻的一個基本特徵,前瞻的另一個特性是不吃字符,意思就是前瞻的做用只是爲了匹配知足前瞻表達式的字符,而不匹配前瞻自己。也就是說前瞻不會修改匹配位置,這麼說我本身都以爲晦澀,咱們仍是來看看代碼吧︽⊙_⊙︽:
1 var str = 'Hello, Hi, I am Handsome Hilary.'; 2 var reg = /H(?!i)e/g; 3 var newStr = str.replace(reg, "T"); 4 console.log(newStr);//Tllo, Hi, I am Handsome Hilary.
注意觀察輸出的字符串,前瞻的做用僅僅是匹配出知足前瞻條件的字符"H",匹配出了"Hello"和"Handsome"當中的H,但同時前瞻不會吃字符,也就是不會改變位置,接下來仍是會緊接着"H"開始繼續往下匹配,這時候匹配條件是"e",因而"Hello"中的"He"就匹配成功了,而"Handsome"中的"Ha"則匹配失敗。
1. /H(?!i)/g --> Hello, Hi, I am Handsome Hilary. 2. /H(?!i)e/g --> Hello, Hi, I am Handsome Hilary.
既然前瞻是非捕獲性的,並且還不吃字符,那麼瞭解到這些特徵後咱們如今終於能夠完成咱們的需求了吧?由於它不吃字符,因此具體的標籤字符還得由咱們本身來吃:
1 var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>'; 2 var reg = /<(\/?)(?!p|\/p).*?>/g; 3 var newStr = str.replace(reg, "<$1p>"); 4 console.log(newStr);//<p>,<p>,<p>,<p>,</p>,</p>,</p>,</p>
聊了這麼半天,終於解決了我們的第一個需求,注意當中的".*?",雖然這裏匹配的是任意字符,可是別忘了,有了前面的負向前瞻,咱們匹配到的都是後面不會緊跟着"p"或者"/p"的字符"<"。
/<(?!p|\/p)/g --> <div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>
注意在這裏用了一個管道符"|"來匹配"\/p",雖然前面已經有了"(\/?)"匹配結束符,可是切記這裏的分組選項不能省略,由於這裏的量詞是能夠出現0次。咱們來試想一下若是用"/<(\/?)(?!p).*?>/g"來匹配"</p>"這個標籤,當量次匹配到"/"的時候,發現能夠匹配,便記錄下來,而後對"/"進行前瞻判斷,可是後面卻接着一個"p"因而不能匹配,丟掉;注意這時"(\/?)"的匹配字符是0個,因而乎轉而對"<"進行前瞻判斷,這裏的"<"後面緊接着的是"/p"而不是"p",因而乎成功匹配,因此這個標籤會被替換掉;並且,因爲以前的分組匹配到的字符是0個,也就是沒有匹配到字符,因此後面的引用是個空串。
1 var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>'; 2 var reg = /<(\/?)(?!p).*?>/g; 3 var newStr = str.replace(reg, "<$1p>"); 4 console.log(newStr);//<p>,<p>,<p>,<p>,</p>,</p>,<p>,</p>
完成了第一個過濾需求,那麼第二個過濾需求也就天然而然的完成了,這時候,就算有那麼五六個標籤須要保留,我們也不用怕了:
1 var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>'; 2 var reg = /<(\/?)(?!p|\/p|div|\/div).*?>/g; 3 var newStr = str.replace(reg, "<$1p>"); 4 console.log(newStr);//<div>,<p>,<p>,<p>,</p>,</p>,</p>,</div>
JS 的正向前瞻只是正則表達式當中一部分,沒至關就這麼一部分還有着這麼多的奧妙呢。
在使用正向前瞻,咱們須要注意的是:
話說,我們的需求就到這了嗎?真的就完了嗎?同窗們以爲過癮不?有些同窗以爲可能差很少了,須要消化一段時間,可是絕對有那麼一部分同窗還徹底沒過癮呢,不要緊,最後留給你們一道思考題,截止到我寫這篇博客爲止,我尚未想出一個解決辦法呢(ง •_•)ง。
需求以下:當你收到一串HTML代碼,須要對這一串HTML代碼過濾,將裏面全部的非<p>或者<div>標籤都改成<p>,而且保留全部標籤的樣式,要求只使用一個正則表達式,例如:
//輸入 var input = '<div class="beautiful">,<p class="provocative">,<h1 class="attractive" id="header">,<span class="sexy">,</span>,</h1>,</p>,</div>'; //輸出 var output = '<div class="beautiful">,<p class="provocative">,<p class="attractive" id="header">,<p class="sexy">,</p>,</p>,</p>,</div>';
若是你有好的解決方案,歡迎在評論區留言,你們一塊兒學習。
devinran —— 《相愛相殺——正則與瀏覽器的愛恨情仇》
Barret Lee —— 《進階正則表達式》