讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議40:正確使用正則表達式分組 >

建議40:正確使用正則表達式分組

所謂分組,就是通過使用小括號語法分隔符來包含一系列字符、字符類,或者重複類量詞,以實現處理各種特殊的字符序列。例如,針對下面的字符串,希望分揀出每個標籤:


var s=\"<html><head><title></title></head><body></body></html>\";


如果使用貪婪模式進行匹配,雖然可以抓取所有標籤,但是並沒有分開每個標籤:


var r=/<.*>/

var a=s.match(r);/*單元素數組[\"<html><head><title></title></head><body></body></html>\"]*/


如果使用惰性模式進行匹配,每次僅能夠抓取一個標籤:


var r=/<.*?>/

var a=s.match(r);//[\"<html>\"]


但是,如果利用分組來進行匹配,則可以獲取每個標籤的名稱:


var r=/(<.*?>)/g;//分組模式

var a=s.match(r);//全局匹配標籤,並存儲到數組a中

for(var i=0;i<a.length;i++){//遍歷數組a,獲取每個標籤的名稱

alert(a[i]);

}


在上面的示例中,通過小括號邏輯分隔符,實現分別存儲每個被匹配的標籤,最後通過這個數組來獲取每個標籤的名稱。注意,對正則表達式來說,小括號表示一個獨立的邏輯域,其匹配的內容將被獨立存儲,這樣就可以以數組形式讀取每個子表達式所匹配的信息。

再看一個示例,假設準備匹配下面這個長字符串:


var s=\"abcdef-abcdef-abcdef-abcdef-abcdef\";


如果不使用分組,那麼可能實現的正則表達式如下:


var r=/abcdef-abcdef-abcdef-abcdef-abcdef/;

var a=s.match(r);//單元素數組[\"abcdef-abcdef-abcdef-abcdef-abcdef\"]


儘管這是可以的,還是有點低效。如果不知道該字符串中到底出現幾次重複,這時候可以使用分組來重寫這個表達式:


var r=/(abcdef-?)*/;//分組模式進行匹配

var a=s.match(r);//[\"abcdef-abcdef-abcdef-abcdef-abcdef\",\"abcdef\"]


在小括號內的匹配模式表示正則表達式的子表達式,而跟隨在小括號後的重複類數量詞將會作用於子表達式,而不是字符「)」。因此,在上面的示例中通過小括號把每個標籤內容作為匹配對象,然後通過重複類星號不但能進行迭代匹配,最終能夠快速實現匹配的目的。

當然,並不限制在分組後使用星號,還可以使用任意重複類數量詞:


var r=/(abcdef-?){5}/;//連續匹配5次子表達式

var r=/(abcdef-?){1,5}/;//最多匹配5次子表達式

var r=/(abcdef-?){0,}/;//匹配任意次子表達式

var r=/(abcdef-?)?/;//最多匹配一次子表達式

var r=/(abcdef-?)+/;//最小匹配一次子表達式


如果混合使用字符、字符類和量詞,甚至可以實現一些相當複雜的分組,例如:


var s=\"<html><html><html><html></html></html></html></html>\";

var r=/<([/s]*?)html(s)*?>/g;

var a=s.match(r);//[\"<html>\",\"<html>\",\"<html>\",\"<html>\",\"</html

>\",\"</html>\",\"</html>\",\"</html>\"]


在上面的正則表達式中,使用了兩個分組:第一個分組中包含了一個字符範圍類,其中可以任意匹配空格或斜槓,文本範圍類附加了一個量詞「*」,表示空格或斜槓可以出現任意次數,為了避免正則表達式的貪婪性,在重複類量詞後面附加了問號(?),使其以惰性模式進行匹配;第二個分組是一個簡單的任意空格匹配。當然可以不分組,但是第一個分組是必需的,因為數量詞作用於子表達式,而不是某個特定的字符。通過上面正則表達式,可以匹配任意形式的<html>標籤,這樣就不用擔心空格或斜槓對匹配語義的影響。在正則表達式中,分組具有極高的應用價值,下面進行簡要說明。

❑把單獨的項目進行分組,以便合成子表達式,這樣就可以像處理一個獨立的字符那樣,使用|、+、*或?等元字符來處理它們。例如:


var s=\"javascript is not java\";

var r=/java(script)?/g;

var a=s.match(r);//[\"javascript\",\"java\"]


上面的正則表達式可以匹配字符串「javascript」,也可以匹配字符串「java」,因為在匹配模式中通過分組可以使用量詞「?」來修飾該子表達式,這樣匹配字符串時,其後既可以有「script」,也可以沒有。

❑在正則表達式中,通過分組可以在一個完整的模式中定義子模式。當一個正則表達式成功地與目標字符串相匹配時,也可以從目標字符串中抽出與小括號中的子模式相匹配的部分。例如:


var s=\"ab=21,bc=45,cd=43\";

var r=/(w+)=(d*)/;

var a=s.match(r);//[\"ab=21\",\"ab\",\"21\"]


在上面的示例中,不僅要匹配出每個變量聲明,還想知道每個變量的名稱及其值。這時如果使用小括號進行分組,把需要獨立獲取的信息作為子表達式,就不僅可以抽出聲明,還可以提取更多的有用的信息。

❑在同一個正則表達式的後部可以引用前面的子表達式。這是通過在字符「」後加一位或多位數字實現的。數字指定了帶括號的子表達式在正則表達式中的位置,如「1」引用的是第一個帶括號的子表達式,「2」引用的是第二個帶小括號的子表達式。例如:


var s=\"<h1>title<h1><p>text<p>\";

var r=/(</?w+>).*1/g;

var a=s.match(r);//[\"<h1>title<h1>\",\"<p>text<p>\"]


在上面的示例中,通過引用前面子表達式匹配的文本來實現成組匹配字符串。

由於子表達式可以嵌套在其他子表達式中,因此它的位置編號是根據左括號的順序來定的。例如,在下面的正則表達式中,嵌套的子表達式(</?w+>)被指定為「2」。


var s=\"<h1>title<h1><p>text<p>\";

var r=/((</?w+>).*2)/g;

var a=s.match(r);//[\"<h1>title<h1>\",\"<p>text<p>\"]


注意,對正則表達式中前面子表達式的引用,指的並不是那個子表達式的模式,而是與模式相匹配的文本。例如,下面這個字符串就無法實現匹配。


var s=\"<h1>title</h1><p>text</p>\";

var r=/((</?w+>).*2)/g;

var a=s.match(r);//null


雖然子表達式(</?w+>)可以匹配「<h1>」,也可以匹配「</h1>」,但是對於「2」來說,它引用的是前面子表達式匹配的文本,而不是它的匹配模式。如果要引用前面子表達式的匹配模式,則必須使用下面正則表達式。


var r=/((</?w+>).*((</?w+>))/g;

var a=s.match(r);//[\"<h1>title</h1>\",\"<p>text</p>\"]