JavaScript正則表達式的匹配模式

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

定義

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

var pattern=/s$/;

運行這段代碼建立一個新的RegExp對象,並將它賦值給變量pattern。這個特殊 的RegExp對象用來匹配全部以字母「s」結尾的字符串。用構造函數RegExp()也能夠 定義個與之等價的正則表達式,代碼以下:程序員

var pattern=new RegExp(「s$」);

正則表達式直接量則與此不一樣,ECMAScript 3規範規定,一個正則表達式直接 量會在執行到它時轉換爲一個RegExp對象,同一段代碼所表示正則表達式直接量的 每次運算都返回同一個對象。ECMAScript 5規範則作了相反的規定,同一段代碼所 表示的正則表達式直接量的每次運算都返回新對象。IE一直都是按照ECMAScript 5 規範實現的,多數最新版本的瀏覽器也開始遵循ECMAScript 5。web

高級語法

非貪婪的重複

通常匹配重複字符是儘量多地匹配,並且容許後續的正則表達式 繼續匹配。所以,咱們稱之爲「貪婪的」匹配。咱們一樣可使用正則表達式進行非貪婪匹配。只須在待匹配的字符後跟隨一個問號即 可:「??」、「+?」、「*?」或「{1,5}?」。好比,正則表達式/a+/能夠匹配一個或多個連續 的字母a。當使用「aaa」做爲匹配字符串時,正則表達式會匹配它的三個字符。但 是/a+?/也能夠匹配一個或多個連續字母a,但它是儘量少地匹配。咱們一樣 將「aaa」做爲匹配字符串,但後一個模式只能匹配第一個a。正則表達式

使用非貪婪的匹配模式所獲得的結果可能和指望並不一致。考慮如下正則表達 式/a+b/,它能夠匹配一個或多個a,以及一個b。當使用「aaab」做爲匹配字符串時, 它會匹配整個字符串。如今再試一下非貪婪匹配的版本/a+?b/,它匹配儘量少的a 和一個b。當用它來匹配「aaab」時,你指望它能匹配一個a和最後一個b。但實際上, 這個模式卻匹配了整個字符串,和該模式的貪婪匹配如出一轍。這是由於正則表達 式的模式匹配老是會尋找字符串中第一個可能匹配的位置。因爲該匹配是從字符串的第一個字符開始的,所以在這裏不考慮它的子串中更短的匹配。shell

選擇、分組和引用

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

注意,選擇項的嘗試匹配次序是從左到右,直到發現了匹配項。若是左邊的選擇項匹配,就忽略右邊的匹配項,即便它產生更好的匹配。所以,當正則表達式 /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*)/

image

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

/[’」][^’」]*[’」]/

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

/([’」])[^’」]*\1/

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

/([’」])[^\1]*\1/

一樣,在正則表達式中不用建立帶數字編碼的引用,也能夠對子表達式進行分組。它不是以「(」和「)」進行分組,而是以「(?:」和「)」來進行分組,好比,考慮下面這 個模式:

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

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

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

image

先行斷言

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

image

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

image

用於模式匹配的String方法

String支持4種使用正則表達式的方法。

search

最簡單的是 search()。它的參數是一個正 則表達式,返回第一個與之匹配的子串的起始位置,若是找不到匹配的子串,它將 返回-1。好比,下面的調用返回值爲4:

「JavaScript」.search(/script/i);

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

replace

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

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

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

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

split

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對象都包含5個屬性。

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

RegExp的方法

RegExp對象定義了兩個用於執行模式匹配操做的方法。

exec

RegExp最主要的執行模式匹配的方法是 exec()exec()方法對一個指定的字符串執行一個正則表達 式,簡言之,就是在一個字符串中執行匹配檢索。若是它沒有找到任何匹配,它就 返回null,但若是它找到了一個匹配,它將返回一個數組,就像match() 方法爲非全局檢索返回的數組同樣。

match() 方法不一樣,無論正則表達式是否具備全局修飾符g, exec()都會返回同樣的數組。當調用exec()的正則表達式對象具備修飾符g時,它將把當前正則表達式 對象的lastIndex屬性設置爲緊挨着匹配子串的字符位置。當同一個正則表達式第二 次調用 exec() 時,它將從 lastIndex 屬性所指示的字符處開始檢索。

var pattern = /Java/g;
var text = "JavaScript is more fun than Java!";
result = pattern.exec(text)
//結果
//["Java", index: 0, input: "JavaScript is more fun than Java!"]
pattern.lastIndex //4

若是 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);
}

test

另一個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 的值,那麼下一次對新字符串進行檢索時,執行檢索的起始位置可能就不是字符串的 開始位置,而多是任意位置。固然,若是RegExp不帶有修飾符g,則沒必要擔憂會發生這種狀況。一樣要記住,ECMAScript 5中,正則表達式直接量的每次計算都會建立一個新的 RegExp對象,每一個新 RegExp對象 具備各自的 lastIndex屬性,這勢必會大大減小「殘留」 lastIndex 對程序形成的意外影響。

參考

相關文章
相關標籤/搜索