轉載:http://omiga.org/blog/archives/714
javascript之正則表達式
定義
在javascript我們可以通過內建的類來定義一個正則表達式。
var reName = new RegExp("omiga");
實際上RegExp類的構造函數可以接受兩個參數,除了本身需要匹配的模式字符串外,還可以定義指定額外處理方式的第二個參數。
var reName = new RegExp("omiga","i"); //忽略大小寫
我很好奇輸出reName會得到什麼結果呢?於是:
document.write(reName);
得到結果:/omiga/i,於是我們得到javascript中正則表達式的第二種定義方法(perl風格):
var reName = /omiga/;
那第二個參數呢?當然,同樣可以爲其指定第二個參數:
var reName = /omiga/i;
這兩種定義方式都是可行的,完全可以根據個人習慣進行選擇。就像可以使用var s = new String(“for a simple life”);定義字符串的同時還可以使用var s = “for a simple life”;來定義是完全相同的。建議使用perl風格的寫法,除了簡潔外,還省去了使用RegExp構造函數定義時需要對“\”轉義的麻煩。
如果要匹配字符“\”,perl風格的寫法是:
var res = /\\/;
而構造函數的寫法則需要對兩個“\”都進行轉義:
var res = new RegExp("\\\\");
感覺上是不是就麻煩了很多?
記住,在一個完整的正則表達式中“\”後面總是跟着另外一個字符。
javascript中的正則表達式
其實上面已經在開始講了javascript對正則表達式的實現方式了,只定義了正則表達式,但是如何在javascript中真正使用正則表達式呢?在javascript中RegExp和String對象都有處理正則表達式的方法。
- test RegExp的test方法用來測試字符串是否匹配給出的匹配模式,返回布爾值;
- exec RegExp的exec方法返回包含第一個匹配的的數組或null;
- match String的match方法返回包含所有匹配子字符串的數組;
- replace String的replace方法完成string的替換操作,支持正則表達式;
- search 與String的indexof方法類似,不同的是search支持正則表達式,而不僅僅是字符串;
- split 按照一定規則拆分字符串並將子字符串存儲到數組中的String方法。
關於這些函數的具體使用方法,可以參閱JS的相關函數手冊。
一個實例對象除了方法當然還有屬性,一個正則表達式有以下屬性:
- global 布爾值,若全局選項g已設置則返回true,否則返回false;
- ignoreCase 布爾值,若忽略大小寫選項i已設置則返回true,否則返回false;
- lastIndex 整數,使用exec或test方法時被填入,表示下次匹配將會從哪個字符位置開始;
- multiline 布爾值,表示多行模式選項m是否設置,若設置則返回true,否則返回false;
- source 正則表達式的元字符串形式。/\\/的source將返回”\\“。
元字符
在正則表達式中有一些特殊的字符符號我們是不能直接使用的,必須對其進行轉義後才能使用。如“\”,因爲這些字符在正則表達式中有特殊的語法含義,這類字符被稱爲元字符,正則表達式中的元字符有:
.,\,/,*,?,+,[,(,),],{,},^,$,|
可能不太好記憶,當無法確定某個字符是否是元字符的時候就勇敢的對其進行轉義是沒有錯的,對不是元字符的字符進行轉義是不會出什麼問題的,但是如果不對元字符轉義就會有意想不到的錯誤產生了。
分組匹配
一個簡單的字符就可以是一個匹配模式,但是現實情況往往不會這麼簡單。比如我們要匹配一個0-9的數字:
var i = 5; var j = 6;
這個正則表達式要如何書寫才能同時匹配這兩個數字呢?簡單的字符表達式當然無法完成了,這個時候我們就可以爲0-9十個數字來定義一個字符集合(字符類)來進行匹配。
var reNum = /[0123456789]/; document.write(reNum.test(i)); //true document.write(reNum.test(j)); //true
使用test方法測試匹配結果都輸出了true。
範圍匹配
上一個例子使用了分組匹配,但是如果要匹配所有26個英文字母,還要包括大小寫,仍然可以使用分組匹配:
var reLetter = /abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/;
恩,這個正則表達式是完全正確的,但是是不是感覺太長了,有沒有辦法讓它更爲簡潔一點?當然是有的,爲字符或數字指定一個匹配範圍就可以了。
var reNum = /[0-9]/; var reLetter = /[a-zA-Z]/;
這樣就可以了,“-”用來定義一個匹配區間,字符的具體順序由ASCII字符表確定,所以不能寫成/A-z/,因爲Z-a之間還包含着其他字符。
取非匹配
很多編程語言中都使用“!”取非操作,包括javascript。正則表達式中也有取非操作,比如/[^0-9]/就是一個取非操作的正則表達式了。
var i = 5; var s = "o"; var rec = /[^0-9]/; document.write(rec.test(i)); //false document.write(rec.test(s)); //true
符號^用來完成取非操作,同時^0-9也是必須包含在[]中的,因爲^其實還有另外一種特殊用途。
特殊字符
可能你覺得/[a-zA-Z]/,/[0-9]/還是不夠簡潔,的確,在正則表達式中一些特定的字符集合可以使用一些特殊的元字符來代替。這些特殊的字符並不是必不可少的,但是卻可以給我們帶來不少方便。/[0-9]/就完全可以寫成這樣:
var reNum = /\d/;
那大小寫字母字符類呢?很遺憾,除了POSIX字符類(javascript不支持POSIX字符類)中有支持大小寫字母的特殊字符類外並沒有專門替代方法。
常見的特殊字符有:
- \d 任何一個數字字符,等價於[0-9]
- \D 任何一個非數字字符,等價於[^0-9]
- \w 任何一個字母數字或下劃線字符,等價於[a-zA-Z_]
- \W 任何一個非字母數字和下劃線字符,等價於[^a-zA-Z_]
- \s 任何一個空白字符,包括換頁符、換行符、回車符、製表符和垂直製表符,等價於[\f\n\r\t\v]
- \S 任何一個非空白字符,等價於[^\f\n\r\t\v]
- . 換行和回車以外的任何單個字符,等價於[^\n\r]
相同字母大小寫總是進行取非操作的。
十六進制和八進制字符
在正則表達式中使用十六進制或八進制字符也是完全可行的,他們所匹配的字符即是由其轉換成十進制後的數值在ASCII中所對應的字符。
var reAt = /\x40/; //十六進制字符\x40(64)對應字符“@” var reA = /\0101/; //八進制字符\0101(65)對應字符“A”
重複匹配
以匹配一個email地址爲例,[email protected]這樣的一個email地址必須包括一個合法的用戶名mymail,@符號以及一個合法的域。其中用戶名和域名的字符個數都是無法判斷的,但是有一點是肯定的——用戶名必須至少是一個字符,域名至少是兩個字符中間還必須有一個點號。於是我們可以這樣做:
var reMail = /\w+@\w+\.\w+/i; var email = "[email protected]"; document.write(reMail.test(email)); //true
“+”表示字符出現一次或多次,至少出現一次。這個正則表達式其實並不能匹配所有合法的email地址,後面我們繼續完善。
除了“+”可以指定至少匹配一次外,還有很多其他的可以指定匹配次數的方式。
- ? 出現零次或一次,最多一次
- * 出現任意次(零次、一次、多次)
- + 出現一次或多次,至少一次
- {n} 能且只能出現n次
- {n,m} 至少出現n次,最多出現m次
www.gogle.com,www.google.com,www.gooogle.com這三個網址都能正確地打開google的首頁,於是就可以用{n,m}匹配其中的1個,2個或3個字母”o”。
var gogle = "www.gogle.com"; var google = "www.google.com"; var gooogle = "www.gooogle.com"; var reGoogle = /w{3}\.go{1,3}gle\.com/i; document.write(reGoogle.test(gogle)); //true document.write(reGoogle.test(google)); //true document.write(reGoogle.test(gooogle)); //true
在上面的正則表達式中,我們使用了{3}來制定字符“w”能且只
能出現3次,用{1,3}來制定字母“o”可以出現1到3次。
防止過度匹配
有這樣一段HTML文本:
var html = "<em>omiga</em>for a simple life<em>http://omiga.org/</em>";
如果現在要講<em></em>及其中間的文本匹配出來,正則表達式可以這樣寫:
var reEm1 = /<em>.*<\/em>/gi; document.write(html.match(reEm1)); //"<em>omiga</em>for a simple life<em>http://omiga.org/</em>" var reEm2 = /<em>.*?<\/em>/gi; document.write(html.match(reEm2)); //<em>omiga</em>,<em>http://omiga.org/</em>
當使用貪婪模式的時候,”.*”會最大程度地進行字符匹配,所以輸出了整個字符串。而在惰性模式中,”.*?”只進行最小限度的匹配,所以完整的輸出了我們需要的字符串。
惰性模式的語法很簡單,即是在貪婪模式後面加上一個“?”即可。
- * –> *?
- + –> +?
- {n,} –> {n,}?
位置匹配
var s = “_Don’t do it!”;
如何將單詞“do”匹配出來?it’s easy!
var reDo = /do/gi; document.write(s.match(reDo)); //Do,do
但是這個簡單的正則表達式/do/gi將“don’t”中的“do”也進行了匹配,可這並不是想要的結果。而在正則表達式中有專門用來進行單詞邊界匹配的限定符”\b“。
var reDo = /\bdo\b/gi; document.write(s.match(reDo)); //do
“\b”到底匹配的什麼呢?”\b”匹配的是一個位置,一個位於”\w“(字母,數字,下劃線)和”\W“之間的位置。
既然有”\b”,那有”\B”嗎?當然,他和”\b“剛好相反,由來匹配一個不是單詞邊界的位置。比如上例中匹配”don’t”中的”do”時”\B”就可派上用場。
var reDo = /\Bdo\B/gi; document.write(s.match(reDo)); //Do
在介紹取非匹配的時候介紹^只用位於[]並緊跟[方能取非匹配,而^還有另外一種用途——字符串邊界匹配。
- ^ 用來匹配字符串開頭
- $ 用來匹配字符串結尾
比如我們要匹配一個http://omiga.org形式的org域名:
var url = "http://omiga.org"; var reUrl = /^(http):\/\/omiga\.(org)$/gi; document.write(reUrl.test(url)); //true
正則表達式reUrl限制url必須以”http”開頭,以”org”結尾。
又如經常被擴展的string方法trim:
function trim(s){ return s.replace(/(^\s*)|(\s*$)/g,""); }
同時我們可以在整個模式的最前面使用(?m)來啓用分行匹配模式。這樣,^不但匹配正常的字符串開頭,還將匹配行分隔符(換行符)後面的開始位置;$不僅匹配正常的字符串結尾,還將匹配行分隔符(換行符)後面的結束位置。
子表達式
上一篇文章的最後部分中的var reUrl = /^(http):\/\/omiga\.(org)$/gi;已經涉及到子表達式了。用來指定重複次數的元字符只能作用於緊挨着它的字符或元字符,而在實際應用中我們需要進行重複匹配的字符往往不一定就只是一個字符或元字符,就如reUrl中所要匹配的“http”和“org”就是多個字符,這時候就可以使用(和)將多個字符括起來作爲一個獨立的元素來使用。
同樣在上一篇文章中所構造的驗證email地址的正則表達式var reMail = /\w+@\w+\.\w+/i;並不完善,一個有效的用戶名除了可以是字母、數字、下劃線外,還可以是點號,同時域名部分也不能保證是mail.com這行的形式,也完全有可能是mail.mymail.com這樣的形式,所以一個更爲完善的匹配有效email地址的正則表達式是這樣的:
var
reEmail = /(\w+\.
)*\w+@(\w+\.
)+\w+/i;
字表達式允許多重嵌套,而且這種嵌套在理論上是沒有限制的,但在實際應用中還是應該根據實際情況適可而止。
回溯引用
在web開發中,我們經常需要去匹配HTML標籤,大多數的HTML標籤都有一個開始標記和結束標記如<h1></h1>,<div></div>,如果只需單純的匹配H1和DIV我們可以很容易的構造出該正則表達式:
var
reH1 = /<h1>.*?<\/h1>/gi;var
reDiv = /<div>.*?<\/div>/gi;
但是我們所要匹配的並不是某個或某幾個HTML標籤,事實上HTML具體是什麼樣的形式我們完全是未知的,比如XML的標記我們是完全無法預計的,所以分組匹配在這裏完全排不上用場。幸運的是,在正則表達式中回溯引用允許正則表達式模式引用前面的匹配結果。具體應用可以參考下面匹配HTML標籤的正則表達式。
var
html ="<h1>omiga</h1>"
;var
reTag = /<(\w+\d?
)>.*?<\/\1>/gi; document.write(html.match(reTag
));//<h1>omiga</h1>
reTag最後部分的\1便是一個回溯引用,引用的前面的第一個子模式(\w+\d?),當然如果前面還存在第二個子模式我們也可以使用\2引用、。注意:回溯引用只能引用前面已經匹配過的結果,而下面這樣的寫法就是錯誤的。
var
reTag = /<\1>.*?<\/(\w+\d?
)>/gi;
回溯引用在替換操作中有着十分廣泛的應用。比如我們要將一段文本中的所有網址自動添加上其對應的超鏈接,即是將“http://omiga.org”的字符串替換成<a href=”http://omiga.org”>omiga</a>的形式。我們就可以這樣處理:
var
url ="http://omiga.org"
;var
reUrl = /(http[s]*:\/{2}(\w+\.
)+\w+)/gi; document.write(url.replace(reUrl,'<a href=
));"$1"
>$1</a>'//<a href=
"http://omiga.org"
>http://omiga.org</a>
$1引用了前面的子模式(http[s]*:\/{2}(\w+\.)+\w+)。注意:javascript中進行替換操作時回溯引用使用”$”而不是”\”。
前後查找
如果我們需要獲取h1標籤中的文本(包含在h1標籤中的文本,不包括h1本身),這個正則表達式應該如何寫?比如”<h1>front-end</h1>”,在所有介紹過的方法中,似乎都還沒有提及過要匹配某個字符串,但卻只返回某些字符前或後的字符串的情況,正則表達式中確實是存在這樣的語法。
var
fe ="<h1>front-end</h1>"
;var
reInnerText = /(?<=<h1>
).*?(?=<\/h1>
))/i;
在reInnerText和/<h1>.*?<\/h1>/i的匹配模式是相同的,唯一不同的返回結果,/<h1>.*?<\/h1>/i會返回整個fe字符串,而reInnerText只返回”front-end”,比較這兩個正則表達式可以發現兩處不同的寫法:(?<=<h1>),(?=<\/h1>)。(?<=<h1>)定義了一個向後查找模式,即匹配結果只包括”<h1>”後面的部分;(?=<\/h1>)則定義的是一個向前查找模式,匹配結果只返回”</h1>”前的結果;所以reInnerText的匹配結果只返回”<h1>”和”</h1>”之間的內容!前後查找的語法很簡單,向前查找是一個以”?=”開頭的字表達式,而向後查找確實一個以”?<=”開頭的字表達式。
遺憾的javascript並不支持正則表達式的向後查找,所以事實上reInnerText的寫法在javascript是有語法錯誤的。有條件可以使用其他支持前後查找的語言進行驗證,比如PHP。
$title ='<h1>front-end</h1>'
;if
(preg_match('/(
,$title,$rst)){ echo $rst[0];?<=<h1>
).*?(?=<\/h1>
)/i'//front-end
}
條件查找
正則表達式還有一種功能強大但卻不被經常用到的功能——嵌入條件查找。在回溯引用一節中所介紹的匹配HTML標籤的正則表達式並沒有考慮諸如<img src=”logo.gif” />這樣的元素,而下面的reImg正式一個使用嵌入條件匹配img元素的正則表達式。
var
reImg = /<(img
)?\s+[^>]+(?(1
)\/)>/i;