正則表達式的模式匹配


正則表達式的模式匹配

正則表達式(regular expression)是一個描述字符模式的對象。JavaScript的RegExp類表示正則表達式,String和RegExp都定義了方法,后者使用正則表達式進行強大的模式匹配和文本檢索與替換功能。JavaScript的正則表達式語法是Perl5的正則表達式語法的大型子集,所以對於有Perl編程經驗的程序員來說,學習JavaScript中的正則表達式由是小菜一碟。


有一些Perl正則表達式語法特性並不被ECMAScript支持,這些特性包括:s (單行模式)
和x (擴展語法)標記;\a、\e、\l、\u、\L、\U、\E、\Q、\A、\Z、\z和\G轉義字苻; "(?<=”正向后行斷言和“(?<!”負向后行斷言;“(?#”注釋和擴展“(?”的語法。


正則表達式的定義

JavaScript中的正則表達式用RegExp對象表示,可以使用RegExp( )構造函數來創建RegExp對象,不過RegExp( )對象更多的是通過一種特殊的直接量語法來創建。就像通過引號包裹字符的方式來定義字符串直接量一樣,正則表達式直接量定義為包含在一對斜杠(/)之間的字符,例如:

var pattern = /s$/;

運行這段代碼創建一個新的RegExp對象,並將它賦值給變量pattern。這個特殊的RegExp對象用來匹配所有以字母“s”結尾的字符串。用構造函數RegExp( )也可以定義個與之等價的正則表達式,代碼如下:

var pattern = new RegExp("s$");

RegExp直接量和對象的創建

就像字符串和數字一樣,程序中每個取值相同的原始類型直接量均表示相同的值, 這是顯而易見的。程序運行時每次遇到對象直接量(初始化表達式)諸如{ }[ ]的時候都會創建新對象。比如,如果在循環體中寫var a =[ ],則每次遍歷都會創建一個新的空數組。

正則表達式直接量則與此不同,ECMAScript 3規范規定,一個正則表達式直接量會在執行到它時轉換為一個RegExp對象,同一段代碼所表示正則表達式直接量的每次運算都返回同一個對象。ECMAScript 5規范則做了相反的規定,同一段代碼所表示的正則表達式直接量的每次運算都返回新對象。IE—直都是按照ECMAScript 5規范實現的,多數最新版本的瀏覽器也開始遵循ECMAScript 5,盡管目前該標准並未全面廣泛推行。


正則表達式的模式規則是由一個字符序列組成的。包括所有字母和數字在內,大多數的字符都是按照直接量僅描述待匹配的字符的。如此說來,正則表達式/java/可以匹配任何包含“java”子串的字符串。除此之外,正則表達式中還有其他具有特殊語義的字符,這些字符並不按照字面含義進行匹配。比如,正則表達式/s$/包含兩個字符,第一 個字符“s”按照字面含義匹配,第二個字符$是一個具有特殊語義的元字符,用以匹配字符串的結束。因此這個正則表達式可以匹配任何以“s”結束的字符串。

直接量字符

正如上文提到的,正則表達式中的所有字母和數字都是按照字面含義進行匹配的。JavaScript正則表達式語法也支持非字母的字符匹配,這些字符需要通過反斜線(\)作為前綴進行轉義。比如,轉義字符\n用以匹配換行符。下表中列出了這些轉義字符。

正則表達式中的直接量字符

字符 匹配
字母和數字字符 自身
\o NUL字符(\u0000)
\t 制表符(\u0009)
\n 換行符(\u000A)
\v 垂直制表符(\u000B)
\f 換頁符(\u000C)
\r 回車符(\u000D)
\xnn 由十六進制數nn指定的拉丁字符,例如,\xOA等價於\n
\uxxxx 由十六進制數xxxx對旨定的Unicode字符,例如\u0009等價於\t
\cX 控制字符^X,例如,\cJ等價於換行符\n

在正則表達式中,許多標點符號具有特殊含義,它們是:

^ $ . * + ? = ! : | \ / ( ) [ ] { }

在接下來,我們將學習這些符號的含義。某些符號只有在正則表達式的某些上下文中才具有某種特殊含義,在其他上下文中則被當成直接量處理。然而,如果想在正則表達式中使用這些字符的直接量進行匹配,則必須使用前綴\,這是一條通行規則。其他標點符號(比如@和引號)沒有特殊含義,在正則表達式中按照字面含義進行匹配。

如果不記得哪些標點符號需要反斜線轉義,可以在每個標點符號前都加上反斜線。另外需要注意,許多字母和數字在有反斜線做前綴時也有特殊含義,所以對於想按照直接量進行匹配的字母和數字,盡量不要用反斜線對其轉義。當然,要想在正則表達式中按照直接量匹配反斜線本身,則必須使用反斜線將其轉義。比如,正則表達式“/\V”用以匹配任何包含反斜線的字符串。

字符類

將直接量字符單獨放進方括號內就組成了字符類(character class)。 一個字符類可以匹配它所包含的任意字符。因此,正則表達式/[abc]/就和字母“a”、“b”、“c”中的 任意一個都匹配。另外,可以通過“^”符號來定義否定字符類,它匹配所有不包含在方括號內的字符。定義否定字符類時,將一個”^”符號作為左方括號內的第一個字符。正 則表達式/[abc]/匹配的是“a”、“b”、“c”之外的所有字符。字符類可以使用連字符來表示字符范圍。要匹配拉丁字母表中的小寫字母,可以使用/[a-z]/,要匹配拉丁字母表中任何字母和數字,則使用/[a-zA-Z0-9]/

由於某些字符類非常常用,因此在JavaScript的正則表達式語法中,使用了這些特殊字符的轉義字符來表示它們。例如,\s匹配的是空格符、制表符和其他Unicode空白符,\S匹配的是非Unicode空白符的字符。下表列出了這些字符,並且總結了字符類的語法(注意,有些字符類轉義字符只能匹配ASCII字符,還沒有擴展到可以處理Unicode字符,但可以通過十六進制表示方法來顯式定義Unicode字符類,例如,/[\u0400-\u04FF]/用以匹配所有的Cyrillic字符譯注)。

正則表達式的字符類

字符 匹配
[...] 方括號內的任意字符
[^...] 不在方括號內的任意字符
. 除換行符和其他Unicode行終止符之外的任意字符
\w 任何ASCII字符組成的單詞,等價於[a-zA-ZO-9]
\W 任何不是ASCII字符組成的單詞,等價於[^-zA-ZO-9]
\s 任何Unicode空白符
\S 任何非Unicode空白符的字符,注意\w和\S不同
\d 任何ASCII數字,等價於[0-9]
\D 除了ASCII數字之外的任何字符,等價於[^0-9]
[\b] 退格直接量(特例)

注意,在方括號之內也可以寫這些特殊轉義字符。比如,由於\s匹配所有的空白字符, \d匹配的是所有數字,因此/[\s\d]/就匹配任意空白符或者數字。注意,這里有一個特例。下面我們將會看到轉義符\b具有的特殊含義,當用在字符類中時,它表示的是退格符,所以要在正則表達式中按照直接量表示一個退格符,只需要使用具有一個元素的字符 類/[\b]/

重復

用剛剛學過的正則表達式的語法,可以把兩位數描述成/\d\d/,四位數描述成/\d\d\d\d/。 但到目前為止,還沒有一種方法可以用來描述任意多位的數字,或者描述由三個字母和 一個數字構成的字符串。這些正則表達式語法中較為復雜的模式都提到了正則表達式中某元素的“重復出現次數”。

我們在正則模式之后跟隨用以指定字符重復的標記。由於某些重復種類非常常用,因此就有一些專門用於表示這種情況的特殊字符。例如,"+”用以匹配前一個模式的一個或多個副本。下表總結了這些表示重復的正則語法。

正則表達式的重復字符語法

字符 含義
{n,m} 匹配前一項至少n次,但不能超過m次
{n,} 匹配前一項n次或者更多次
{n} 匹配前一項n次
? 匹配前一項0次或者1次,也就是說前一項是可選的,等價於{0,1}
+ 匹配前一項1次或多次,等價於{1,}
* 匹配前一項o次或多次,等價於{0,}

這里有一些例子:

/\d{2,4}/         // 匹配2~4個數字
/\w{3}\d?/        // 精確匹配三個單詞和一個可選的數字
/\s+java \s+/     // 匹配前后帶有一個或多個空格的字符串"java" 
/[^(]*/           // 匹配一個或多個非左括號的字符

在使用“*和“?”時要注意,由於這些字符可能匹配0個字符,因此它們允許什么都不匹配。例如,正則表達式/a*/實際上與字符串“bbbb”匹配,因為這個字符串含有0個a。

非貪婪的重復

下表中列出的匹配重復字符是盡可能多地匹配,而且允許后續的正則表達式繼續匹配。因此,我們稱之為“貪婪的”匹配。我們同樣可以使用正則表達式進行非貪婪匹配。只須在待匹配的字符后跟隨一個問號即可:“??”、“ + ?”、"*?”或 “{1,5}?”。比如,正則表達式/a+/可以匹配一個或多個連續的字母a。當使用“aaa”作為匹配字符串時,正則表達式會匹配它的三個字符。但是/a+?/也可以匹配一個或多個連續字母a,但它是盡可能少地匹配。我們同樣將“aaa”作為匹配字符串,但后一個模式只能匹配第一個a。

使用非貪婪的匹配模式所得到的結果可能和期望並不一致。考慮以下正則表達式 /a+b/,它可以匹配一個或多個a,以及一個b。當使用“aaab”作為匹配字符串時,它會 匹配整個字符串。現在再試一下非貪婪匹配的版本/a+?b/,它匹配盡可能少的a和一個b。當用它來匹配“aaab”時,你期望它能匹配一個a和最后一個b但實際上,這個模式卻匹配了整個字符串,和該模式的貪婪匹配一模一樣。這是因為正則表達式的模式匹配總是會尋找字符串中第一個可能匹配的位置。由於該匹配是從字符串的第一個字符開始的,因此在這里不考慮它的子串中更短的匹配。

選擇、分組和引用

正則表達式的語法還包括指定選擇項、子表達式分組和引用前一子表達式的特殊字符。字 符T用於分隔供選擇的字符。例如,/ab|cd|ef/可以匹配字符串“ab”,也可以匹配字符串“cd”,還可以匹配字符串“ef”。/\d{3}|[a-z]{4}/匹配的是三位數字或者四個小寫字母。

注意,選擇項的嘗試匹配次序是從左到右,直到發現了匹配項。如果左邊的選擇項匹配,就忽略右邊的匹配項,即使它產生更好的匹配。因此,當正則表達式/a|ab/匹配字符串“ab”時,它只能匹配第一個字符。

正則表達式中的圓括號有多種作用。一個作用是把單獨的項組合成子表達式,以便可以像處理一個獨立的單元那樣用“|”、“*”、"+”或者“?”等來對單元內的項進行處理。 例如,/java(script)?/可以匹配字符串"java”,其后可以有“script”也可以沒有。/(ab|cd)+|ef/可以匹配字符串“ef”,也可以匹配字符串“ab”或“cd”的一次或多次重復。

在正則表達式中,圓括號的另一個作用是在完整的模式中定義子模式。當一個正則表達式成功地和目標字符串相匹配時,可以從目標串中抽出和圓括號中的子模式相匹配的部分。例如,假定我們正在檢索的模式是一個或多個小寫字母后面跟隨了一位或多位數字,則可以使用模式/[a-z]+\d+/。但假定我們真正關心的是每個匹配尾部的數字,那么如果將模式的數字部分放在括號中(/[a-z]+(\d+)/),就可以從檢索到的匹配中抽取數字了。

帶圓括號的表達式的另一個用途是允許在同一正則表達式的后部引用前面的子表達式。這是通過在字符“\”后加一位或多位數字來實現的。這個數字指定了帶圓括號的子表達式在正則表達式中的位置。例如,\1引用的是第一個帶圓括號的子表達式,\3引用的是第三個帶圓括號的子表達式。注意,因為子表達式可以嵌套另一個子表達式,所以它的位置是參與計數的左括號的位置。例如,在下面的正則表達式中,嵌套的子表達式([Ss]cript)可以用\2來指代:

/([Jj]ava([Ss]cript)?)\sis\s(fun\w*)/

對正則表達式中前一個子表達式的引用,並不是指對子表達式模式的引用,而指的是與那個模式相匹配的文本的引用。這樣,引用可以用於實施一條約束,即一個字符串各個單獨部分包含的是完全相同的字符。例如,下面的正則表達式匹配的就是位於單引號或雙引號之內的0個或多個字符。但是,它並不要求左側和右側的引號匹配(即,加入的兩個引號都是單引號或都是雙引號):

/['"][^'"]*['"]/

如果要匹配左側和右側的引號,可以使用如下的引用:

/(['"])[^'"]*\1/

\1匹配的是第一個帶圓括號的子表達式所匹配的模式。在這個例子中,存在這樣一條約束,那就是左側的引號必須和右側的引號相匹配。正則表達式不允許用雙引號括起的內容中有單引號,反之亦然。不能在字符類中使用這種引用,所以下面的寫法是非法的:

/(['"])[^\1]*\1/

同樣,在正則表達式中不用創建帶數字編碼的引用,也可以對子表達式進行分組。它不是以“(”和“)”進行分組,而是以和“)”來進行分組,比如,考慮下面這個模式:

/([Jj]ava(?:[Ss]cript)?)\sis\s(fun\w*)/

這里,子表達式(?:[Ss]cript)僅僅用於分組,因此復制符號“?”可以應用到各個分組。這種改進的圓括號並不生成引用,所以在這個正則表達式中,\2引用了與(fun\W*)匹配的文本。

下表對正則表達式的選擇、分組和引用運算符做了總結。

正則表達式的選擇、分組和引用字符

字符 含義
` `
(...) 組合,將幾個項組合為一個單元,這個單元可通過“*”、“+”、“?”和“`
(?:...) 只組合,把項組合到一個單元,但不記憶與該組相匹配的字符
\n 和第n個分組第一次匹配的字符相匹配,組是圓括號中的子表達式(也有可能是嵌套的),組索引是從左到右的左括號數,形式的分組不編碼

指定匹配位置

正如前面所介紹的,正則表達式中的多個元素才能夠匹配字符串的一個字符。例如,\s匹配的只是一個空白符。還有一些正則表達式的元素匹配的是字符之間的位置,而不是實際的字符。例如,\b匹配一個單詞的邊界,即位於\w(ASCII單詞)字符和\W(非ASCII單詞)之間的邊界,或位於一個ASCII單詞與字符串的開始或結尾之間的邊界(除了在字符類(方括號)中,\b匹配退格符。)。像\b這樣的元素不匹配某個可見的字符,它們指定匹配發生的合法位置。有時我們稱這些元 素為正則表達式的錨,因為它們將模式定位在搜索字符串的特定位置上。最常用的錨元素是^,它用來匹配字符串的開始,錨元素$用以匹配字符串的結束。

例如,要匹配單詞“JavaScript”,可以使用正則表達式/^JavaScript$/,如果想匹配 “Java”這個單詞本身(不像在“JavaScript”中作為單詞的前綴),可以使用正則表達式/\s\Javas/,可以匹配前后都有空格的單詞“Java”。但是這樣做有兩個問題,第一,如果“Java”出現在字符串的開始或者結尾,就匹配不成功,除非開始和結尾處各有一個空格。第二個問題是,當找到了與之匹配的字符串時,它返回的匹配字符串的前端和后端都有空格,這並不是我們想要的。因此我們使用單詞的邊界\b來代替真正的空格符\s進行匹配(或定位)。這樣正則表達式就寫成了/\bJava\b/。元素\B將把匹配的錨點定位在不是單詞的邊界之處。因此,正則表達式/\B[Ss]cript/與“JavaScript”和“postscript”匹配,但不與“script”和“Scripting”匹配。

任意正則表達式都可以作為錨點條件。如果在符號"(?=”和“)”之間加入一個表達式,它就是一個先行斷言,用以說明圓括號內的表達式必須正確匹配,但並不是真正意義上的匹配。比如,要匹配一種常用的程序設計語言的名字,但只在其后有冒號時才匹配,可以使用/[Jj]ava([Ss]cript)?(?=\:)/。這個正則表達式可以匹 配"JavaScript: The Definitive Guide” 中的"JavaScript”,但是不能匹配"Java in a Nutshell”中的“Java”,因為它后面沒有冒號。

帶有“(?!”的斷言是負向先行斷言,用以指定接下來的字符都不必匹配。例如,/Java(?! Script)([A-Z]\w*)/可以匹配"Java”后跟隨一個大寫字母和任意多個ASCII單詞,但Java后面不能跟隨“Script”。它可以匹配"JavaBeans",但不能匹配“Javanese”;它可以匹配“JavaScript”,但不能匹配”JavaScripter”。

下表總結了正則表達式中的錨。

正則表達式中的錨字符

字符 含義
A 匹配字符串的開頭,在多行檢索中,匹配一行的開頭
$ 匹配字符串的結尾,在多行檢索中,匹配一行的結尾
\b 匹配一個單詞的邊界,簡言之,就是位於字符\w\W之間的位置,或位於字符\w和字符串的開頭或者結尾之間的位置(但需要注意,[\b]匹配的是退格符)
\B 匹配非單詞邊界的位置
(?=p) 零寬正向先行斷言,要求接下來的字符都與p匹配,但不能包括匹配p的那些字符
(?!p) 零寬負向先行斷言,要求接下來的字符不與p匹配

修飾符

正則表達式中的語法還有最后一個知識點,即正則表達式的修飾符,用以說明高級匹配模式的規則。和之前討論的正則表達式語法不同,修飾符是放在“/”符號之外的,也就是說,它們不是出現在兩條斜線之間,而是第二條斜線之后。JavaScript支持三個修飾符,修飾符“i”用以說明模式匹配是不區分大小寫的。修飾符“g”說明模式匹配應該是全局的,也就是說,應該找出被檢索字符串中所有的匹配。修飾符“m”用以在多行模式中執行匹配,在這種模式下,如果待檢索的字符串包含多行,那么^$錨字符除了匹配整個字符串的開始和結尾之外,還能匹配每行的開始和結尾。比如正則表達式/java$/im可以匹配“java”也可以匹配“Java\nis fun"。

這些修飾符可以任意組合,比如,要想不區分大小寫匹配字符串中的第一個單詞 “java” ( “Java” 或 “JAVA” 等),可以使用不區分大小寫的修飾符來定義正則表達式/\bjava\b/i。要想匹配字符串中所有的單詞,則需要添加修飾符g:/\bjava\b/gi

正則表達式修飾符

字符 含義
i 執行不區分大小寫的匹配
g 執行一個全局匹配,簡言之,即找到所有的匹配,而不是在找到第一個之后就停止
m 多行匹配模式,s匹配一行的開頭和字符串的開頭,$匹配行的結束和字符串的結束

用於模式匹配的String方法

String支持4種使用正則表達式的方法。最簡單的是search( )。它的參數是一個正則表達式,返回第一個與之匹配的子串的起始位置,如果找不到匹配的子串,它將返回-1。比如,下面的調用返回值為4:

"JavaScript".search(/script/i);

如果search( )的參數不是正則表達式,則首先會通過RegExp構造函數將它轉換成正則表達式,search( )方法不支持全局檢索,因為它忽略正則表達式參數中的修飾符g。

replace( )方法用以執行檢索與替換操作。其中第一個參數是一個正則表達式,第二個參數是要進行替換的字符串。這個方法會對調用它的字符串進行檢索,使用指定的模式來匹配。如果正則表達式中設置了修飾符g,那么源字符串中所有與模式匹配的子串都將替換成第二個參數指定的字符串;如果不帶修飾符g,則只替換所匹配的第一個子串。如果replace( )的第一個參數是字符串而不是正則表達式,則replace( )將直接捜索這個字符串,而不是像search( )一樣首先通過RegExp( )將它轉換為正則表達式。比如,可以使用下面的方法,利用replace( )將文本中的所有javascript(不區分大小寫)統一替換為“JavaScript”:

// 將所有不區分大小寫的javascript都替換成大小寫正確的JavaScript 
text.replace(/javascript/gi, "JavaScript");

但replace( )的功能遠不止這些。回憶一下前文所提到的,正則表達式中使用圓括號括起來的子表達式是帶有從左到右的索引編號的,而且正則表達式會記憶與每個子表達式匹配的文本。如果在替換字符串中出現了$加數字,那么replace( )將用與指定的子表達式相匹配的文本來替換這兩個字符。這是一個非常有用的特性。比如,可以用它將一個 字符串中的英文引號替換為中文半角引號:

// 一段引用文本起始於引號,結束於引號
// 中間的內容區域不能包含引號
var quote = /"([^"]*)"/g;|
// 用中文半角引號替換英文引號,同時要保持引號之間的內容(存儲在$1中)沒有被修改 
text.replace(quote, ' “$1” ');

值得注意的是,replace( )方法的第二個參數可以是函數,該函數能夠動態地計算替換字符串。

match( )方法是最常用的String正則表達式方法。它的唯一參數就是一個正則表達式(或通過RegExp( )構造函數將其轉換為正則表達式),返回的是一個由匹配結果組成的數組。如果該正則表達式設置了修飾符g,則該方法返回的數組包含字符串中的所有匹配結果。例如:

"1 plus 2 equals 3".match(/\d+/g) // 返回["1", "2", "3"]

如果這個正則表達式沒有設置修飾符g,match( )就不會進行全局檢索,它只檢索第一個匹配。但即使match( )執行的不是全局檢索,它也返回一個數組。在這種情況下,數組的第一個元素就是匹配的字符串,余下的元素則是正則表達式中用圓括號括起來的子表達式。因此,如果match( )返回一個數組a,那么a[0]存放的是完整的匹配,a[1]存放的則是與第一個用圓括號括起來的表達式相匹配的子串,以此類推。為了和方法replace( )保持一致,a[n]存放的是$n的內容。

例如,使用如下的代碼來解析一個URL:

var url = /(\w+):\/\/([\w.]+)\/(\S*)/;
var text = "Visit my blog at http://www.example.com/~david";
var result = text.match(url); 
if (result != null) {
    var fullurl = result[0];  // 包含 "http://www.example.com/~david" 
    var protocol = result[1];  // 包含 "http"
    var host = result[2];      // 包含 "www.example.com"
    var path = result[3];      // 包含 "~david"

值得注意的是,給字符串的match( )方法傳入一個非全局的正則表達式,實際上和給這個正則表達式的exec( )方法傳入的字符串是一模一樣的,它返回的數組帶有兩個屬性:index和input,接下來對exec( )方法的討論中會提到:

String對象的最后一個和正則表達式相關的方法是split( )。這個方法用以將調用它的字符串拆分為一個子串組成的數組,使用的分隔符是split( )的參數,例如:

"123,456,789".split(","); // 返回["123","456","789"]

split( )方法的參數也可以是一個正則表達式,這使得split( )方法異常強大。例如,可以指定分隔符,允許兩邊可以留有任意多的空白符:

"1, 2, 3, 4, 5".split(/\s*,\s*/); // 返回["1","2","3","4","5"]

RegExp對象

正則表達式是通過RegExp對象來表示的。除了RegExp( )構造函數之外,RegExp對象還支持三個方法和一些屬性。接下來的兩節會對RegExp模式匹配方法和屬性展開講述。

RegExp( )構造函數帶有兩個字符串參數,其中第二個參數是可選的,RegExp( )用以創建新的RegExp對象。第一個參數包含正則表達式的主部分,也就是正則表達式直接量中兩條斜線之間的文本。需要注意的是,不論是字符串直接量還是正則表達式,都使用“\”字符作為轉義字符的前綴,因此當給RegExp( )傳入一個字符串表述的正則表達式 時,必須將“\”替換成“\\” 。RegExp( )的第二個參數是可選的,如果提供第二個參數, 它就指定正則表達式的修飾符。不過只能傳入修飾符g、i、m或者它們的組合。比如:

// 全局匹配字符串中的5個數字,注意這里使用了"\\",而不是"\"
var zipcode = new RegExp("\\d{5}”, "g");

RegExp( )構造函數非常有用,特別是在需要動態創建正則表達式的時候,這種情況往往沒辦法通過寫死在代碼中的正則表達式直接量來實現。例如,如果待檢索的字符串是由用戶輸入的,就必須使用RegExp( )構造函數,在程序運行時創建正則表達式。(其實通過eval( )也可以實現運行時動態創建正則表達式,但不推薦使用eval( ))

RegExp的屬性

每個RegExp對象都包含5個屬性。屬性source是一個只讀的字符串,包含正則表達式的文本。屬性global是一個只讀的布爾值,用以說明這個正則表達式是否帶有修飾符g。屬性ignoreCase也是一個只讀的布爾值,用以說明正則表達式是否帶有修飾符i。屬性multiline是一個只讀的布爾值,用以說明正則表達式是否帶有修飾符m。最后一個屬性lastindex,它是一個可讀/寫的整數。如果匹配模式帶有g修飾符,這個屬性存儲在整個字符串中下一次檢索的開始位置,這個屬性會被exec( )和test( )方法用到,下面會講到。

RegExp的方法

RegExp對象定義了兩個用於執行模式匹配操作的方法。它們的行為和上文介紹過的String方法很類似。RegExp最主要的執行模式匹配的方法是exec( ),它與String方法match( )相似,只是RegExp方法的參數是一個字符串,而String方法的參數是一個RegExp對象。exec( )方法對一個指定的字符串執行一個正則表達式,簡言之, 就是在一個字符串中執行匹配檢索。如果它沒有找到任何匹配,它就返回null,但如果它找到了一個匹配,它將返回一個數組,就像match( )方法為非全局檢索返回的數組一樣。這個數組的第一個元素包含的是與正則表達式相匹配的字符串,余下的元素是與圓括號內的子表達式相匹配的子串。屬性index包含了發生匹配的字符位置,屬性input引用的是正在檢索的字符串。

和match( )方法不同,不管正則表達式是否具有全局修飾符g,exec( )都會返回一樣的數組。回憶一下,當match( )的參數是一個全局正則表達式時,它返回由匹配結果組成的數組。相比之下,exec( )總是返回一個匹配結果,並提供關於本次匹配的完整信息。當調用exec( )的正則表達式對象具有修飾符g時,它將把當前正則表達式對象的lastindex屬性設置為緊挨着匹配子串的字符位置。當同一個正則表達式第二次調用exec( )時,它將從lastIndex屬性所指示的字符處開始檢索。如果exec( )沒有發現任何匹配結果,它會將lastindex重置為0(在任何時候都可以將lastindex屬性設置為0,每當在字符串中找最后一個匹配項后,在使用這個RegExp對象開始新的字符串査找之前,都應當將lastindex設置為0)。這種特殊的行為使我們可以在用正則表達式匹配字符串的過程中反復調用exec( ),比如:

var pattern = /Java/g;
var text = "JavaScript is more fun than Java!";
var result;
while((result = pattern.exec(text)) != null) { 
     alert("Matched '" + result[0] + "'" +
     "at position " + result.index +
     "; next search begins at " + pattern.lastIndex);
}

另外一個RegExp方法是test( ),它比exec( )更簡單一些。它的參數是一個字符串,用test( )對某個字符串進行檢測,如果包含正則表達式的一個匹配結果,則返回true:

var pattern = /java/i;
pattern.test("JavaScript");     // 返回 true

調用test( )和調用exec( )等價,當exec( )的返回結果不是null時,test( )返回true。由於這種等價性,當一個全局正則表達式調用方法test( )時,它的行為和exec( )相同,因為它從lastIndex指定的位置處開始檢索某個字符串,如果它找到了一個匹配結果,那么它就立即設置lastIndex為當前匹配子串的結束位置。這樣一來,就可以使用test( )來遍歷字符串,就像用exec( )方法一樣。

與exec( )和test( )不同,String方法search( )、replace( )和match( )並不會用到lastIndex屬性。實際上,String方法只是簡單地將lastIndex屬性值重置為0。如果讓一個帶有修飾符g的正則表達式對多個字符串執行exec( )或test( ),要么在每個字符串中找出所有的匹配以便將lastIndex自動重置為零,要么顯式將lastindex手動設置為0(當最后一次檢索失敗時需要手動設置lastindex)。如果忘了手動設置lastIndex的值,那么下一次對新字符串進行檢索時,執行檢索的起始位置可能就不是字符串的開始位置,而可能是任意位置(這里所說的任意位置實際上是由lastindex的值決定的,如果lastindex的值不為0,必定會對新開始的正則表達式匹配檢索造成不確定的影響。)。當然,如果RegExp不帶有修飾符g,則不必擔心會發生這種情況。同樣要記住,在ECMAScript 5中,正則表達式直接量的每次計算都會創建一個新的RegExp對象,每個新RegExp對象具有各自的lastIndex屬性,這勢必會大大減少“殘留”lastlndex對程序造成的意外影響。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM