JavaScript 中的正則表達式

正則表達式的模式匹配

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

正則表達式的意義

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

var patterns = /s$/;

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

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

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

RegExp直接量和對象的建立

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

正則表達式直接量則與此不一樣,ECMAScript 3規範規定,一個正則表達式直接量會在執行到它時轉換爲一個RegExp對象,同一段代碼所表示正則表達式直接量的每次運算都返回同一個對象ECMAScript 5規範則作了相反的規定,同一段代碼所表示的正則表達式直接量的每次運算都返回新對象。IE一直都是按照ECMAScript 5的規範實現的,多數最新版本的瀏覽器也開始遵循ECMAScript 5,儘管目前該標準併爲全面普遍推行。以下列代碼:shell

function getReg() {
    var reg = /[a-z]/;
    reg.foo = "bar";
    return reg;
}
var reg = getReg();
var reg2 = getReg();
console.log(reg === reg2); // 在Firefox 3.6中返回true,在Firefox 4+中返回flase
reg.foo = "baz";
console.log(reg2.foo); // 在Firefox 3.6中返回「baz」,在Firefox 4+中返回「bar」

緣由能夠在ECMAScript 5規範第24頁和第247頁中找到,也就是說在ECMAScript 3規範中,用正則表達式建立的RegExp對象會共享一個實例,而在ECMAScript 5中則是兩個獨立的實例。很明顯ECMAScript 5的規範更符合開發者的指望。express

直接量字符串

正如上文提到的,正則表達式中的全部字母和數字都是按照字面含義進行匹配的。JavaScript正則表達式也支持非字母的字符匹配,這些字符經過反斜線()做爲前綴進行轉義。好比,轉義字符n用來匹配換行符。編程

  • o NUL字符(u0000)後端

  • t 製表符(u0009)數組

  • n 換行符(u000A)

  • v 垂直製表符(u000B)

  • f 換頁符(u000C)

  • r 回車符(u000D)

  • xnn 有十六進制數nn指定爲拉丁字符,例如,x0A等價於n

  • uxxxx 由十六進制數xxxx指定的Unicode字符,例如u0009等價於t

  • cX 控制字符^X,例如,cJ等價於換行符n

在正則表達式中,許多(共18個)標點符號具備特殊含義,它們是

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

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

若是不記得那些標點符號須要反斜線轉移,可使用每一個標點符號前都加上反斜線。另外須要注意,許多字符和數字在有反斜線作前綴是也有特殊含義`,因此對於想按照直接量進行匹配的字母和數字,進行不要用反斜線對其轉義。固然,想要在正則表達式中按照直接量匹配反斜線自己,則必須使用反斜線將其轉義。好比,正則表達式「/\/」用以匹配任何包含反斜線的字符串。

字符類

直接量字符串單獨放進方括號內就組成了字符類(character class)。一個字符類能夠匹配它所包含的任意字符。所以,正則表達式/[abc]/就和字母「a」、「b」、「c」中的任意一個都匹配。定義否認字符類時,將一個「^」字符作爲左方括號內的第一字符。正則表達式/[^abc]/匹配的是「a」、「b」、「c」以外的全部字符。字符類可使用連字符來表示字符範圍。要匹配拉丁字母表中的小寫字母,可使用/[a-z]/,要匹配拉丁字母表中任何字母和數字,則使用/[a-zA-Z0-9]/

因爲某些字符類很是經常使用,由於在JavaScript的正則表達式語法中,使用了這些特殊字符的轉義字符來表示它們。例如,s匹配的是空格符、製表符和其餘Unicode空白符(\o、 \t、 \n、 \v、 \f、 \r等),S匹配的是非Unicode 空白符的字符。下面列出了這些字符,而且總結了字符類的語法(注意,有些字符類轉義字符只能匹配ASCII字符,尚未擴展到能夠處理Unicode字符,但能夠經過十六進制表示方法來顯示定義Unicode字符類,例如,/[u2e80-u9fff]/用來匹配全部漢字)。

  • [...] 方括號內的任意字符

  • [^...] 不在方括號內的任意字符

  • . 除換行符和其餘Unicode行終止符以外的任意字符

  • w 任何ASCII字符組成的單詞,等價於[a-zA-Z0-9]

  • W 任何不適ASCII字符組成的單詞,等價於[^a-zA-Z0-9]

  • s 任何Unicode空白符

  • S 任何非Unicode空白符的字符,注意W和S的不一樣

  • d 任何ASCII數字,等價於[0-9]

  • D 除了ASCII數字以外的任何字符,等價於[^0-9]

  • [b] 推格直接量(特例)

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

重複

用剛剛學過的正則表達式的語法,能夠把兩位數描述成/dd/,四位數描述成/dddd/。可是目前爲止,尚未一種方法能夠用來描述任意多位的數字活着描述由三個字母和一個數字構成的字符串。這些正則表達式語法中較爲複雜的模式都提到了正則表達式中某元素的"重複出現次數"。

咱們在正則模式以後跟隨用以制定字符重複的標記。因爲某些重複種類很是經常使用,所以就有一些專門用於表示這種狀況的特殊字符。例如,「+」用以匹配前一個模式的一個或多個副本。下面總結了這些表示重複的正則語法。

  • {n,m} 匹配前一項至少n次,但不能超過m次

  • {n,} 匹配前一項n次或者更屢次

  • {n} 匹配前一項n次

  • ? 匹配前一項0次或者1次,也就是說前一項是可選的,等價於{0,1}

  • + 匹配前一項1次或者屢次,等價於{1,}

  • * 匹配前一項0次或者屢次,等價於{0,}

這裏有一些例子:

var reg1 = /\d{2,4}/  // 匹配2~4個數字
var reg2 = /\w{3}\d?/  // 精確匹配三個單詞和一個可選數字
var reg3 = /\s+java\s+/  // 匹配先後帶有一個或多個空格的字符串「java」
var reg4 = /[^(]*/  // 匹配一個或多個非左括號的字符

在使用「*」和「?」時要注意,因爲這些字符可能匹配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。但實際上,這個模式卻匹配了整個字符串,和該模式的貪婪匹配一摸同樣。這是由於正則表達式的模式匹配老是會尋找字符串中第一個可能匹配的位置。因爲該匹配是從字符串的第一個字符開始的。所以在這裏不考慮他的子串中更短的匹配。

選擇、分組和引用

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

注意,選擇項的嘗試匹配次序是從左到右,直到發現匹配項。若是左邊的選擇項匹配,就會忽略右邊的匹配項,即便它產生更好的匹配。所以,當正則表達式/a|ab/匹配字符串「ab」時,它只能匹配第一個字符。

正則表達式中的圓括號有多種做用。第一個做用是把單獨的項組合成子表達式,以即可以像處理一個獨立的單元那樣用「|」、「 * 」、「 + 」或者「 ? 」等來對單元內的項進行處理。例如,/java(Script)?/能夠匹配字符串「java」,其後能夠有「Script」也能夠沒有。/(ab|cd)+|ef/能夠匹配字符串「ef」,也能夠匹配字符串「ab」或者「cd」的一次或屢次重複。例如,將「font-size」改成「fontSize」:

var str = "font-size";
str = str.replace(/-(\w)/,function(str,$1){
    return $1.toUpperCase();
});
console.log(str); // => fontSize

在正則表達式中,圓括號的另外一個做用是在完整的模式中定義子模式。當一個正則表達式成功地和目標字符串相互匹配時,能夠從目標字符串中抽出和圓括號彙總的字母是相匹配的部分(咱們將在隨後的部分中看到如何取得這些匹配的子串)。例如,嘉定咱們正在檢索的模式是一個或多個小寫字母后面跟隨了一位或多位數字,則可使用模式/[a-z]+d+/。但假定咱們真正關心的是每一個匹配尾部的數字,那麼若是將模式的數字部分放在括號中(/[a-z]+(d+)/),就能夠從檢索到的匹配中抽取數字了。以下:

var reg = /[a-z]+\d+/;
var str = 'abcde123';
str.match(reg); 
// => ["abcde123"]
var reg2 = /[a-z]+(\d+)/;
str.match(reg2); 
// => ["abcde123", "123"]

帶圓括號的表達式的另外一個用途就是容許在同一正則表達式的後部引用前面的子表達式。這是經過在字符「」後加一位或多位數字來實現的。這個數字指定了帶圓括號的子表達式在正則表達式中的位置。例如,1引用的是第一個帶圓括號的子表達式,3引用的是第三個帶圓括號的子表達式。注意,由於子表達式能夠嵌套另一個子表達式,因此它的問題是參與計數的左括號的位置。例如,下面的正則表達式中,嵌套的子表達式([Ss]cript)可使用2來指代

var reg = /([Jj]ava([Ss]cript)?)\sis\s(fun\w*)/;
var str = 'javascript is fun that java';
var match = str.match(reg);
console.log(match);
// => ["javascript is fun", "javascript", "script", "fun"]

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

var reg = /['"][^'"]*['"]/;
var str = '"hello\'';
reg.test(str); // => true

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

var reg = /(['"])[^'"]*\1/;
var str = '"hello\'';
reg.test(str); // => false

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

var reg = /(['"])[^\1]*\1/;
var str = '"hello\'';
reg.test(str); // => false

正如上面重點標註的那段說明。對正則表達式中前一個子表達式的引用,並非只對子表達式的引用,而是指與那個模式相匹配的文本的引用 因此這個是false。

在接下來,咱們會看到一種帶圓括號的子表達式的引用,這是正則表達式的檢索和替換操做的強大特性之一。

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

var reg = /([Jj]ava(?:[Ss]cript)?)\sis\s(fun\w*)/;
var str = 'javascript is fun that java';
var match = str.match(reg);
// => ["javascript is fun", "javascript", "fun"]

咱們會發現這裏匹配的結果跟前面匹配的結果「["javascript is fun", "javascript", "script", "fun"]」相比,少了一個"script"。這是由於子表達式(?:[sS]cript)僅僅用於分組,不參與引用。所以複製符號"?"能夠應用到各個分組。這種改進的圓括號並不生成引用,因此這個正則表達式中,2引用了與(funW*)匹配的文本。

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

  • | 選擇,匹配的是該符號左邊的子表達式或右邊的子表達式

  • (...) 組合,將幾個項組合爲一個單元,這個單元可經過「 * 」、「 ? 」、「 + 」和「 | 」等符號加以修飾,並且能夠記住和這個組合相匹配的字符串以供此後的引用使用

  • (?:...) 只組合,把項組合到一個單元,但不記憶和改組相匹配的字符

  • \n 和第n個分組第一次匹配的字符相匹配,組是圓括號中的子表達式(也多是嵌套的),組索引是從左到右的左括號,「(?:」形式的分組不參與編碼

指定匹配位置

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

例如,要匹配單詞「javascript」,可使用正則表達式/^[Jj]ava[Ss]cript$/。若是想匹配"java"這個單詞自己(不像在「JavaScript」中做爲單詞的前綴),可使用正則表達式/sJava/,能夠匹配先後都有空格的單詞「java」。可是這樣作有兩個問題,第一,若是「java」出如今字符串的開始或者結尾,就是匹配不成功,除非開始和結尾處各有一個空格。第二個問題是,當找到了與之匹配的字符串時,它返回的匹配字符串的前端和後端都有空格沒這並非咱們想要的。所以咱們使用單詞的邊界b來代替真正的空格符s進行匹配(或定位)。這樣正則表達式就寫成了/b[Jj]avab/。元素B將把匹配的錨點定位在不適單詞的邊界之處。所以正則表達式/B[Ss]cript/於「JavaScript」和「posrscript」匹配,可是不與「script」和「Scripting」 匹配。

var reg =/\bjava\b/;

var str = 'javascript is more fun that java';
str.match(reg); // => ["java"]

var str2 = 'javascript is more fun that javac';
str.match(reg); // => null

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

var reg =/[Jj]ava([Ss]cript)?(?=\:)/;
var str1 = "Javascript: beautiful language";
str1.match(reg); // => ["Javascript", "script"]
var str2 = "java in a Nutshell";
str2.match(reg); // => null

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

var reg = /Java(?!Script)[A-Z]\w*/;
var str = "JavaBeans";
str.match(reg); // => ["JavaBeans"]
  • ^ 匹配字符串的開頭,再多行檢索中,匹配一行的開頭

  • $ 匹配字符串的結尾,再多行檢索中,匹配一行的結尾

  • \b 匹配一個單詞的邊界,簡言之,就是位於字符w和W之間的位置,或位於字符W和匹配字符串的開頭或者結尾的位置

  • \B 匹配非單詞邊界的位置

  • (?=pattern) 正向先行斷言,要就接下來的字符都與pattern匹配,可是不能包含匹配pattern的那些字符

  • (?!pattern) 負向(反向)先行斷言,要就接下來的字符都不與pattern匹配

修飾符

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

var reg = /java$/im;
var str1 = 'java';
str1.match(reg); // => ["java"]
var str2 = 'java\n is fun';
str2.match(reg); // => ["java"]

這些修飾符能夠任意組合,好比,要想不區分大小寫匹配字符串中的第一個單詞「java」,可使用不區分大小寫的修飾符來定義正則表達式/bjavab/i。要想匹配字符串中全部單詞,則須要添加修飾符g:/bjavab/gi

  • i 執行不區分大小寫的匹配

  • g 執行一個全局匹配,簡言之,即找到全部的匹配,而不是在找到第一個以後中止

  • m 多行匹配模式,^匹配一行的開頭和字符串的開頭,$匹配行的結尾和字符串的結尾

獲取指定的querystring

function param(key, url) {
    var reg = new RegExp("(?:^|\\?|#|&)" + key + "=([^&#]*)(?:$|&|#)", "i");
    var o = reg.exec(url || location.href);
    return o ? encodeURI(o[1]) : "";
}

獲取全部的querystring

function getAllParam() {
    var reg=/[?&]([^=?&]+)=([^=?&]+)/ig
    var url={};
    while(reg.exec(location.href)){
        url[RegExp.$1]=RegExp.$2;
    }
    return url;
}
相關文章
相關標籤/搜索