有點復雜,是轉載的文章
貪婪模式與非貪婪模式講解
Javascript中的正則貪婪與非貪婪模式的區別是:
- 被量詞修飾的子表達式的匹配行為;
- 貪婪模式在整個表達式匹配成功的情況下盡可能多的匹配;
- 非貪婪模式在整個表達式匹配成功的前提下,盡可能少的匹配;
一些常見的修飾貪婪模式的量詞如下:
1. 什么是貪婪模式?
var str = "longen<p>我是中國人</p>yunxi<p>我是男人</p>boyboy";
// 貪婪模式 匹配所有字符
console.log(str.match(/<p>.*<\/p>/)[0]);
// <p>我是中國人</p>yunxi<p>我是男人</p>
// 后面加問號,變成非貪婪模式
console.log(str.match(/<p>.*?<\/p>/)[0]); // <p>我是中國人</p>
我們來理解下匹配的基本原理;
首先正則是 /<p>.*</p>/ 匹配;<p>匹配字符串第一個字符匹配失敗,接著往下,直接匹配到<p>時候,匹配成功,接著把匹配的控制權交給.*,從匹配到的<p>位置開始匹配,一直到</p>之前,接著把控制權交給</p>,接著在當前位置下往下匹配,因此匹配到</p>,匹配成功;由于它是貪婪模式,在匹配成功的前提下,仍然會嘗試向右往下匹配,因此會匹配到兩個<p>標簽結束;但是非貪婪模式,也就是第二個console.log();他匹配到第一個p標簽成功后,它就不會再進行匹配,因此打印的值為一個p標簽的內容了;
理解匹配成功前提下的含義: 上面我們解釋了,貪婪模式是要等一個表達式匹配成功后,再往下匹配;比如我們現在接著再看下們的表達式匹配代碼:
var str = "longen<p>我是中國人</p>yunxi<p>我是男人</p>boyboy";
// 貪婪模式
console.log(str.match(/<p>.*<\/p>yunxi/)[0]);
//打印 <p>我是中國人</p>yunxi
如上代碼,打印出一個p標簽內容,我們知道.*是貪婪模式匹配,如果按照上面的解釋會打印出2個p標簽的內容,因為這里在匹配到p標簽后,會繼續匹配后面的yunxi字符串,直到整個表達式匹配成功,才提示匹配成功,因此當匹配到第一個yunxi后,第一個子表達式匹配成功,接著嘗試往右繼續匹配,<p></p>都匹配成功,但是yunxi不會匹配boyboy,所以第二次嘗試匹配的子表達式匹配失敗,因此只返回匹配成功后的第一個p標簽的內容了;
我們現在再來看看非貪婪模式的含義:
測試代碼:
var str = "longen<p>我是中國人</p>yunxi<p>我是男人</p>boyboy<p>我是中國人2</p>yunxi<p>我是男人</p>boyboy";
// 非貪婪模式1
console.log(str.match(/<p>.*?<\/p>boyboy/)[0]);
//<p>我是中國人</p>yunxi<p>我是男人</p>boyboy
// 貪婪模式
console.log(str.match(/<p>.*<\/p>yunxi/)[0]);
//<p>我是中國人</p>yunxi<p>我是男人</p>boyboy<p>我是中國人2</p>yunxi
我們先看表達式1,/<p>.*?</p>boyboy/ 匹配str字符串,首先先匹配到p標簽內容;但是由于boyboy字符串一直沒有匹配到,因此會一直嘗試往后匹配,直到匹配到boyboy字符串后,才匹配成功,否則匹配失敗;由于它是非貪婪模式,因此這時候它不會再往下進行匹配,所以匹配就結束了;因此第一個console輸出為<p>我是中國人</p>yunxi<p>我是男人</p>boyboy;
我們可以再來看看貪婪模式 第二個console.log()輸出的; 正則表達式 /<p>.*</p>yunxi/ 匹配到第一個p標簽yunxi后,由于它是貪婪的,它還想接著向右繼續匹配,直到匹配完成后,匹配成功,才結束,因此把所有p標簽后面跟隨yunxi的字符串都匹配到,且之間的所有的字符串都被返回;
理解正則表達式匹配原理
我們先來理解下占有字符和零寬度的含義。
1. 占有字符和零寬度
在正則表達式匹配的過程中,如果子表達式匹配到的是字符內容,而非位置的話,并被保存在匹配的結果當中,那么就認為該子表達式是占有字符的;如果子表達式匹配的僅僅是位置,或者說匹配中的內容不保存到匹配的結果當中,那么就認為該子表達式是零寬度的。我們先來理解下零寬度的列子,最常見的就是環視~ 它只匹配位置;比如順序環視;環視待會就講;
正則匹配方式 /abc/
匹配過程:首先由字符a取得控制權,從位置0開始進行匹配,a匹配a,匹配成功;接著往下匹配,把控制權交給b,那么現在從位置1開始,往下匹配,匹配到字符串b,匹配成功,接著繼續往下匹配,位置是從2開始,把控制權交給c,繼續往下匹配,匹配到字符串c,匹配成功,所以整個表達式匹配成功;匹配結果為 abc 匹配的開始位置為0,結束位置為3;
含有匹配優先量詞的匹配過程
源字符串abc,正則表達式為ab?c ;量詞?可以理解為匹配優先量詞,在可匹配可不匹配的時候,會優先選擇匹配;當匹配不到的時候,再進行不匹配。先匹配b是否存在,如果不存在的話,就不匹配b;因此結果可以匹配的有 abc,ac等
匹配過程:
首先由字符a取得控制權,從位置0開始匹配,a匹配到字符串a,匹配成功;接著繼續匹配,把控制權交給b,b現在就從位置1開始匹配;匹配到字符串b,匹配成功;接著就把控制權交給c,c從位置2開始繼續匹配,匹配字符串c,匹配成功;整個表達式匹配成功;假如b那會兒匹配不成功的話,它會忽略b,繼續匹配字符串c,也就是如果匹配成功的話,結果是ac;
因此abc匹配字符串abc,匹配的位置從0開始,到3結束。
如果匹配的結果為ac的話,那么匹配的位置從0開始,到2結束;
假如我們把字符串改為abd,或者abe等其他的,那么當匹配到最后一個字符的時候,就匹配失敗;
含有忽略優先量詞的匹配過程
量詞?? 含義是 忽略優先量詞,在可匹配和可不匹配的時候,會選擇不匹配,這里的量詞是修飾b字符的,所以b?? 是一個整體的。匹配過程如下
首先由字符a取得控制權,從位置0開始匹配,有”a”匹配a,匹配成功,控制權交給b?? ;首先先不匹配b,控制權交給c,由c來匹配b,匹配失敗,此時會進行回溯,由b??來進行匹配b,匹配成功,然后會再把控制權交給c,c匹配c,匹配成功,因此整個表達式都匹配成功;
理解正則表達式----環視
環視只進行子表達式匹配,不占有字符,匹配到的內容不保存到最終的匹配的結果,是零寬度的,它匹配的結果就是一個位置;環視的作用相當于對所在的位置加了一個附加條件,只有滿足了這個條件,環視子表達式才能匹配成功。環視有順序和逆序2種,順序和逆序又分為肯定和否定,因此共加起來有四種;但是javascript中只支持順序環視,因此我們這邊來介紹順序環視的匹配過程;
如下說明:
1.(?=Expression): 順序肯定環視,含義是所在的位置右側位置能夠匹配到regexp.
2.(?!Expression): 順序否定環視,含義是所在的位置右側位置不能匹配到regexp
順序肯定環視
首先我們需要明白的是:^和$ 是匹配的開始和結束位置的;?= 是順序肯定環視,它只匹配位置,不會占有字符,因此它是零寬度的。這個正則的含義是:
以字母或者數字組成的,并且第一個字符必須為小寫字母開頭;
匹配過程如下:
首先由元字符^取得控制權,需要以字母開頭,接著控制權就交給 順序肯定環視 (?=[a-z]); 它的含義是:要求它所在的位置的右側是有a-z小寫字母開頭的才匹配成功,字符a12,第一個字符是a,因此匹配成功;我們都知道環視都是匹配的是一個位置,不占有字符的,是零寬度的,因此位置是0,把控制權交給[a-z0-9]+,它才是真正匹配字符的,因此正則[a-z0-9]+從位置0開始匹配字符串a12,且必須以小寫字母開頭,第一個字母是a匹配成功,接著繼續從1位置匹配,是數字1,也滿足,繼續,數字2也滿足,因此整個表達式匹配成功;最后一個$符合的含義是以字母或者數字結尾的;
順序否定環視
當順序肯定環視匹配成功的話,順序否定環視就匹配失敗,當順序肯定環視匹配失敗的話,那么順序否定環視就匹配成功;
源字符串:aa<p>one</p>bb<div>two</div>cc
正則:<(?!/?p\b)[^>]+>
正則的含義是:匹配除<p>之外的其余標簽;
如下圖:
匹配過程如下:
首先由”<” 取得控制權,從位置0開始匹配,第一個位置和第二個位置都是字符a,因此匹配失敗~ 接著從位置2匹配,匹配到<, 匹配成功了,現在控制權就交給(?!/?p\b);?!是順序否定環視,只匹配一個位置,不匹配字符,這個先不用管,首先是 /? 取得控制權,它的含義是:可匹配/,或者不匹配/, 接著往下匹配的是p字符,匹配失敗,進行回溯,不匹配,那么控制權就到一位了p字符,p匹配p,匹配成功,控制權就交給\b; \b的含義是匹配單詞的邊界符,\b就匹配到了 > ,結果就是匹配成功,子表達式匹配就完成了;/?p\b 就匹配成功了;所以(?!/?p\b) 這個就匹配失敗了;從而使表達式匹配失敗;我們繼續往下匹配,從b字符開始,和上面一樣匹配失敗,當位置是從14開始的時候 < 字符匹配到”<”,匹配成功,把控制權又交給了(?!/?p\b), 還是/?取得控制權,和上面匹配的邏輯一樣,最后?p\b匹配失敗了,但是(?!/?p\b) 就匹配成功了,因此這一次表達式匹配成功;如下代碼匹配:
var str = "aa<p>one</p>bb<div>two</div>cc";
// 匹配的結果為div,位置從14開始 19結束
console.log(str.match(/<(?!/?p\b)[^>]+>/)[0]);
理解正則表達式---捕獲組
捕獲組就是把正則表達式中子表達式匹配的內容,保存到內存中以數字編號或顯示命名的組里,方便后面使用;可以在正則表達式內部使用,也可以在外部使用;
捕獲組有2種,一種是捕獲性分組,另一種是 非捕獲性分組;
我們先來看看捕獲性的分組語法如下:
捕獲組分組語法:(Expression)
我們都知道中括號是表示范圍內選擇,大括號表示重復次數,小括號的含義是允許重復多個字符;
捕獲性分組的編號規則:編號是按照”(”出現的順序,從左到右,從1開始進行編號;
比如如下代碼:
// 分組的列子
console.log(/(longen){2}/.test("longen")); // false
console.log(/(longen){2}/.test("longenlongen")); //true
// 分組的運用 RegExp.$1 獲取小括號的分組
var str = 11122;
/(\d+)/.test(str);
console.log(RegExp.$1); // 11122
// 使用replace替換 使用分組 把內容替換
var num = "11 22";
var n = num.replace(/(\d+)\s*(\d+)/,"$2 $1");
console.log(n); // 22 11
反向引用
反向引用標識由正則表達式中的匹配組捕獲的子字符串。每個反向引用都由一個編號或名稱來標識;并通過 ”\編號” 表示法來進行引用;
// 反向引用
console.log(/(longen)\1/.test("longen")); // false
console.log(/(longen)\1/.test("longenlongen")); // true
理解非捕獲性分組
并不是所有分組都能創建反向引用,有一種分組叫做非捕獲性分組,它不能創建反向引用,要創建一個非捕獲性分組,只要在分組的左括號的后面緊跟一個問號與冒號就ok;非捕獲分組的含義我們可以理解為如下:子表達式可以作為被整體修飾但是子表達式匹配的結果不會被存儲;如下:
// 非捕獲性分組
var num2 = "11 22";
/#(?:\d+)/.test(num2);
console.log(RegExp.$1); //""
我們再來看下使用 非捕獲性分組來把頁面上的所有標簽都去掉,如下代碼:
// 把頁面上所有的標簽都移除掉
var html = "<p><a >我來測試下</a>by <em>龍恩</em></p>";
var text = html.replace(/<(?:.|\s)*?>/g, "");
console.log(text); // 我來測試下by 龍恩
如上:我們來分析下:正則/<(?:.|\s)?>/g 的含義是:g是修飾符,全局匹配的含義;使用非捕獲性分組?: 的含義是 子表達式可以作為被整體修飾但是子表達式匹配的結果不會被存儲;因此:正則/<(?:.|\s)?>/g 的含義變為:匹配以< 開頭 及 > 結束的所有字符;(?:.|\s)? 含義是:. 代表任意字符,| 含義是或者的意思,\s 是匹配空格的意思;號修飾符的含義是零個或者多個的意思;后面的?(問號)代表可匹配,可不匹配的含義;優先是可匹配;總起來的意思是:全局匹配字符串html 中的 以<開頭 以>結尾的所有字符 替換成 空字符串,因此留下來就是文本;當然我們使用捕獲性分組也可以得到同樣的結果~
反向引用詳細講解
捕獲性分組取到的內容,不僅可以在正則表達式外部通過程序進行引用,也可以在正則表達式內部進行引用,這種引用方式就叫做反向引用。
反向引用的作用是:是用來查找或限定重復,查找或限定指定標識配對出現等。
捕獲性分組的反向引用的寫法如:\number
Number是十進制數字,即捕獲組的編號。
反向引用的匹配原理
捕獲分組在匹配成功時,會將子表達式匹配到的內容,保存到內存中一個以數字編號的組里,可以簡單的認為是對一個局部變量進行了賦值,這時就可以通過反向引用,引用這個局部變量的值。一個捕獲分組在匹配成功之前,它的內容可以是不確定的,一旦匹配成功了,它的內容就確定了,反向引用的內容也就確定了。
比如如下代碼:
var str = "longenaabcd";
console.log(str.match(/([ab])\1/)[0]);//aa
代碼分析:對于如上代碼中的正則 /([ab])\1/, 捕獲組中子表達式[ab];可以匹配a,也可以匹配b,但是如果匹配成功的話,那么它的反向引用也就確定了,如果捕獲分組匹配到的是a,那么它的反向引用就只能匹配a,如果捕獲分組匹配到的是b,那么它的反向引用就只能匹配到b;\1的含義是 捕獲分組匹配到是什么,那么它必須與捕獲分組到是相同的字符;也就是說 只能匹配到aa或者bb才能匹配成功;
該正則匹配的過程我們可以來分析下:
字符串匹配正則/([ab])\1/, 在位置0處開始匹配,0處字符是l,很明顯不滿足,把控制權就交給下一個字符,一直到第6個字符,才匹配到a,匹配成功,把控制權交給\1, 也就是反向引用和分組中是相同的字符,因此也匹配a,字符串中下一個字符也是a,因此匹配成功,因此整個表達式找到匹配的字符,匹配的位置開始于6,結束與8;我們再可以匹配b,原理和上面一樣,這里就不再多解釋了;