1、正則表達(dá)式概述
ECMAScript 3 開始支持正則表達(dá)式,其語法和 Perl 語法很類似,一個(gè)完整的正則表達(dá)式結(jié)構(gòu)如下:
var expression = / pattern / flags ;
其中,模式(pattern)部分可以是任何簡(jiǎn)單或復(fù)雜的正則表達(dá)式,可以包含字符類、限定符、分組、向前查找以及反向引用。
每個(gè)正則表達(dá)式都可帶有一或多個(gè)標(biāo)志(flags),用以標(biāo)明正則表達(dá)式的行為,正則表達(dá)式支持下列 3 個(gè)標(biāo)志:
g: 表示全局(global)模式,即模式將被應(yīng)用于所有字符串,而非在發(fā)現(xiàn)第一個(gè)匹配項(xiàng)時(shí)立即停止;
i : 表示不區(qū)分大小寫(case-insensitive)模式,即在確定匹配項(xiàng)時(shí)忽略模式與字符串的大小寫;
m:表示多行(multiline)模式,即在到達(dá)一行文本末尾時(shí)還會(huì)繼續(xù)查找下一行中是否存在與模式匹配的項(xiàng)。
如果多個(gè)標(biāo)志同時(shí)使用時(shí),則寫成:gmi。
正則表達(dá)式的創(chuàng)建有兩種方式:new RegExp(expression)和 直接字面量。
//使用直接字面量創(chuàng)建
var exp1 = /(^\s+)|(\s+$)/g;
//使用RegExp對(duì)象創(chuàng)建
var exp2 = new RegExp("(^\\s+)|(\\s+$)","g");
exp1 和exp2 是兩個(gè)完全等價(jià)的正則表達(dá)式,需要注意的是,傳遞給 RegExp構(gòu)造函數(shù)的兩個(gè)參數(shù)都是字符串,不能把正則表達(dá)式字面量傳遞給 RegExp 構(gòu)造函數(shù)。
與其他語言中的正則表達(dá)式類似,模式中使用的所有元字符都必須轉(zhuǎn)義。正則表達(dá)式中的元字符包括:
( [ { \ ^ $ | ) ? * + .] }
這些元字符在正則表達(dá)式中都有一或多種特殊用途,因此如果想要匹配字符串中包含的這些字符,就必須對(duì)它們進(jìn)行轉(zhuǎn)義。
//匹配 .docx
var exp = /\.docx/gi ;
由于 RegExp 構(gòu)造函數(shù)的模式參數(shù)是字符串,所以在某些情況下要對(duì)字符進(jìn)行雙重轉(zhuǎn)義。所有元字符都必須雙重轉(zhuǎn)義,那些已經(jīng)轉(zhuǎn)義過的字符也是如此。
// 對(duì) \. 再次轉(zhuǎn)義
var exp = new RegExp("\\.docx","gi");
//匹配 \n
var exp1 = /\\n/g; //對(duì)\n中的\轉(zhuǎn)義
var exp2 = new RegExp("\\\\n","g"); // 對(duì) \\n 再次轉(zhuǎn)義
2、() [] {} 的區(qū)別
() 的作用是提取匹配的字符串。表達(dá)式中有幾個(gè)()就會(huì)得到幾個(gè)相應(yīng)的匹配字符串。比如 (\s+) 表示連續(xù)空格的字符串。
[]是定義匹配的字符范圍。比如 [a-zA-Z0-9]表示字符文本要匹配英文字符和數(shù)字。
{}一般用來表示匹配的長(zhǎng)度,比如 \d{3}表示匹配三個(gè)數(shù)字,\d{1,3} 表示匹配1~3個(gè)數(shù)字,\d{3,}表示匹配3個(gè)以上數(shù)字。
3、^和$
^ 匹配一個(gè)字符串的開頭,比如 (^a)就是匹配以字母a開頭的字符串
$匹配一個(gè)字符串的結(jié)尾,比如 (b$)就是匹配以字母b結(jié)尾的字符串
^ 還有另個(gè)一個(gè)作用就是取反,比如[^xyz] 表示匹配的字符串不包含xyz
注意問題:
如果 ^出現(xiàn)在[ ]中一般表示取反,而出現(xiàn)在其他地方則是匹配字符串的開頭。
^ 和$ 配合可以有效匹配完整字符串:/d+/.test('4xpt') -> true,而 /^\d+$/.test('4xpt')->false
4、\d \s \w .
\d 匹配一個(gè)非負(fù)整數(shù), 等價(jià)于[0-9]
\s匹配一個(gè)空白字符
\w 匹配一個(gè)英文字母或數(shù)字,等價(jià)于[0-9a-zA-Z]
. 匹配除換行符以外的任意字符,等價(jià)于[^\n]
5、* + ?
*表示匹配前面元素0次或多次,比如(\s*)就是匹配0個(gè)或多個(gè)空格
+ 表示匹配前面元素1次或多次,比如 (\d+)就是匹配由至少1個(gè)整數(shù)組成的字符串
?表示匹配前面元素0次或1次,相當(dāng)于{0,1} ,比如(\w?) 就是匹配最多由1個(gè)字母或數(shù)字組成的字符串
6、$1和\1
$1-$9存放著正則表達(dá)式中最近的9個(gè)正則表達(dá)式的提取的結(jié)果,這些結(jié)果按照子匹配的出現(xiàn)順序依次排列?;菊Z法是:RegExp.$n ,這些屬性是靜態(tài)的,除了replace中的第二個(gè)參數(shù)可以省略 RegExp之外,其他地方使用都要加上 RegExp 。
//使用RegExp訪問
/(\d+)-(\d+)-(\d+)/.test("2016-03-26")
RegExp.$1 // 2016
RegExp.$2 // 03
RegExp.$3 // 26
//在replace中使用
"2016-03-26".replace(/(\d+)-(\d+)-(\d+)/,"$1年$2月$3日")
// 2016年03月26日
\1表示后向引用,是指在正則表達(dá)式中,從左往右數(shù),第1個(gè)()中的內(nèi)容,以此類推,\2表示第2個(gè)(),\0表示整個(gè)表達(dá)式。
//匹配日期格式,表達(dá)式中的\1代表重復(fù)(\-|\/|.)
var rgx = /\d{4}(\-|\/|.)\d{1,2}\1\d{1,2}"/
rgx.test("2016-03-26") //true
rgx.test("2016-03.26") //false
兩者的區(qū)別是:\n只能用在表達(dá)式中,而$n只能用在表達(dá)式之外的地方。
7、test和match
前面的大都是JS正則表達(dá)式的語法,而test則是用來檢測(cè)字符串是否匹配某一個(gè)正則表達(dá)式,如果匹配就會(huì)返回true,反之則返回false
/\d+/.test("123") ; //true
/\d+/.test("abc") ; //false
match是獲取正則匹配到的結(jié)果,以數(shù)組的形式返回
"186a619b28".match(/\d+/g); // ["186","619","28"]
8、replace
replace 本身是JavaScript字符串對(duì)象的一個(gè)方法,它允許接收兩個(gè)參數(shù):
replace([RegExp|String],[String|Function])
第1個(gè)參數(shù)可以是一個(gè)普通的字符串或是一個(gè)正則表達(dá)式
第2個(gè)參數(shù)可以是一個(gè)普通的字符串或是一個(gè)回調(diào)函數(shù)
如果第1個(gè)參數(shù)是 RegExp,JS會(huì)先提取RegExp匹配出的結(jié)果,然后用第2個(gè)參數(shù)逐一替換匹配出的結(jié)果
如果第2個(gè)參數(shù)是回調(diào)函數(shù),每匹配到一個(gè)結(jié)果就回調(diào)一次,每次回調(diào)都會(huì)傳遞以下參數(shù):
result: 本次匹配到的結(jié)果
$1,...$9: 正則表達(dá)式中有幾個(gè)(),就會(huì)傳遞幾個(gè)參數(shù),$1~$9分別代表本次匹配中每個(gè)()提取的結(jié)果,最多9個(gè)
offset:記錄本次匹配的開始位置
source:接受匹配的原始字符串
9、經(jīng)典案例
(1) 實(shí)現(xiàn)字符串的trim函數(shù),去除字符串兩邊的空格。
String.prototype.trim = function(){
//方式一:將匹配到的每一個(gè)結(jié)果都用""替換
return this.replace(/(^\s+)|(\s+$)/g,function(){
return "";
});
//方式二:和方式一的原理相同
return this.replace(/(^\s+)|(\s+$)/g,'');
};
^\s+ 表示以空格開頭的連續(xù)空白字符,\s+$ 表示以空格結(jié)尾的連續(xù)空白字符,加上()就是將匹配到的結(jié)果提取出來,由于是 | 的關(guān)系,因此這個(gè)表達(dá)式最多會(huì)match到兩個(gè)結(jié)果集,然后執(zhí)行兩次替換:
String.prototype.trim = function(){
/**
* @param rs:匹配結(jié)果
* @param $1:第1個(gè)()提取結(jié)果
* @param $2:第2個(gè)()提取結(jié)果
* @param offset:匹配開始位置
* @param source:原始字符串
*/
this.replace(/(^\s+)|(\s+$)/g,function(rs,$1,$2,offset,source){
//arguments中的每個(gè)元素對(duì)應(yīng)一個(gè)參數(shù)
console.log(arguments);
});
};
" abcd ".trim();
輸出結(jié)果:
[" ", " ", undefined, 0, " abcd "] //第1次匹配結(jié)果
[" ", undefined, " ", 5, " abcd "] //第2次匹配結(jié)果
(2) 提取瀏覽器 url 中的參數(shù)名和參數(shù)值,生成一個(gè)key/value 的對(duì)象。
function getUrlParamObj(){
var obj = {};
//獲取url的參數(shù)部分
var params = window.location.search.substr(1);
//[^&=]+ 表示不含&或=的連續(xù)字符,加上()就是提取對(duì)應(yīng)字符串
params.replace(/([^&=]+)=([^&=]*)/gi,function(rs,$1,$2){
obj[$1] = decodeURIComponent($2);
});
return obj;
}
/([^&=]+)=([^&=]*)/gi 每次匹配到的都是一個(gè)完整key/value,形如xxxx=xxx, 每當(dāng)匹配到一個(gè)這樣的結(jié)果時(shí)就執(zhí)行回調(diào),并傳遞匹配到的key和 value,對(duì)應(yīng)到$1和$2。
(3) 擴(kuò)展 typeof,包含引用類型的具體類型。
function getDataType(obj){
let rst = Object.prototype.toString.call(obj);
rst = rst.replace(/\[object\s(\w+)\]/,'$1'); //[object Xxx]
return rst.toLowerCase()
}
getDataType(1); //number
getDataType('a'); //string
getDataType(null); //null
getDataType([]); //array
$1是正則表達(dá)式中第一個(gè)()中匹配的內(nèi)容。需要注意的是,replace的第二個(gè)參數(shù)只能是字符串或函數(shù),因此,這里的$1需要放在引號(hào)中。
(4) 在字符串指定位置插入新字符串。
String.prototype.insetAt = function(str,offset){
offset = offset + 1;
//使用RegExp()構(gòu)造函數(shù)創(chuàng)建正則表達(dá)式
var regx = new RegExp("(^.{"+offset+"})");
return this.replace(regx,"$1"+str);
};
"abcd".insetAt('xyz',2); //在c字符后插入xyz
>> "abcxyzd"
當(dāng)offset=2時(shí),正則表達(dá)式為:(^.{3}) .表示除\n之外的任意字符,{3}表示匹配前三個(gè)連續(xù)字符,加()就會(huì)將匹配到的結(jié)果提取出來,然后通過replace將匹配到的結(jié)果替換為新的字符串,形如:結(jié)果=結(jié)果+str
(5) 將手機(jī)號(hào)12988886666轉(zhuǎn)化成129****6666 。
function telFormat(tel){
tel = String(tel);
//方式一
return tel.replace(/(\d{3})(\d{4})(\d{4})/,function (rs,$1,$2,$3){
return $1+"****"+$3
});
//方式二
return tel.replace(/(\d{3})(\d{4})(\d{4})/,"$1****$3");
}
(\d{3}\d{4}\d{4})可以匹配完整的手機(jī)號(hào),并分別提取前 3 位、4-7 位和 8-11位,"3" 是將第 2 個(gè)匹配結(jié)果用****代替并組成新的字符串,然后替換完整的手機(jī)號(hào)。
(6) 實(shí)現(xiàn)HTML編碼,將< / > " & `等字符進(jìn)行轉(zhuǎn)義,避免XSS攻擊 。
function htmlEncode(str) {
//匹配< / > " & `
return str.replace(/[<>"&\/`]/g, function(rs) {
switch (rs) {
case "<":
return "<";
case ">":
return ">";
case "&":
return "&";
case "\"":
return """;
case "/":
return "/"
case "`":
return "'"
}
});
}
另:常用正則表達(dá)式
//將數(shù)轉(zhuǎn)為帶,號(hào)的貨幣形式
'1231232341'.replace(/\B(?=(\d{3})+(?!\d))/g,',');
'1231232341'.replace(/(\d)(?=(?:\d{3})+$)/g,'$1,');
//去掉括號(hào)及其內(nèi)容
'www(sdfsdf)'.replace(/\([^\)]*\)/g,'');
//匹配#000-#fff表示法的顏色
"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$"
//匹配rgb和rgba的顏色
"^[rR][gG][Bb][Aa]?[\(]((2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),){2}(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),?(0\.\d{1,2}|1|0)?[\)]{1}$"
匹配規(guī)則
下面將正則中的一些基本的匹配規(guī)則列出來如下表所示:
要點(diǎn)
貪與不貪
舉個(gè)例子,假設(shè)有以下這段html字符,我想拿到a標(biāo)簽中的內(nèi)容:
<a>南京長(zhǎng)江大橋</a>哈哈<a>南京市長(zhǎng)江大橋</a>
然后我寫了這樣一個(gè)正則: <a>(.)*</a>
在線測(cè)試的結(jié)果如下:
這個(gè)結(jié)果與我們的預(yù)期不符,正常我應(yīng)該得到兩個(gè)匹配的結(jié)果才對(duì),但是現(xiàn)在卻只匹配到一個(gè)結(jié)果。
現(xiàn)在把剛剛的正則改成這樣: <a>(.)*?</a>
在線測(cè)試的結(jié)果如下:
貪 說的是正則在不約束的情況下會(huì)繼續(xù)自動(dòng)向右進(jìn)行匹配,直到匹配結(jié)束,只要匹配的數(shù)據(jù)與正則的最后一個(gè)值匹配就算是匹配到了。
不貪 說的是只要匹配到就結(jié)束,不繼續(xù)向右進(jìn)行匹配了。
問號(hào) ? 就解決了貪婪的問題,使得問號(hào)前面的字符匹配到之后就結(jié)束,但是并不是把 ? 放在哪里都可以解決貪婪的,在正則里,有一些屬于貪婪模式量詞,比如以下這些:
{m,n}{m,}?*+
斷言與零寬
在java中我們知道 斷言 可以用來聲明一個(gè)應(yīng)該為 true 的事實(shí),只有當(dāng)斷言為真時(shí)才會(huì)繼續(xù)進(jìn)行后續(xù)的操作。
在正則中也有 斷言 的概念,但是在正則中除了 斷言 還有 零寬 的概念。
- 斷言:
通俗點(diǎn)將斷言就是 “我斷定某某情況是真的” ,而正則中的斷言,就是說正則可以斷定在 指定的內(nèi)容 的 前面 或 后面 會(huì)出現(xiàn)滿足指定規(guī)則的內(nèi)容。比如 "aa1bb2cc3",正則可以用斷言找出 bb2 前面有 aa1,也可以找出 bb2 后面有 cc3。
- 零寬:
零寬就是沒有寬度,在正則中,斷言只是匹配位置,不占字符,也就是說,匹配結(jié)果里是不會(huì)返回?cái)嘌员旧淼摹?/p>
斷言一共有四種情況:
讓我們來舉個(gè)例子來說明吧,假設(shè)我們現(xiàn)在拿到了某個(gè)網(wǎng)頁的html,里面有個(gè)閱讀數(shù)的標(biāo)簽:
-
<span class="read-cnt">閱讀數(shù):1024</span>
現(xiàn)在我們要獲取到這個(gè)閱讀數(shù),該怎么辦呢?
如果用正向先行斷言來匹配的話,可以這樣來寫:
-
\d+(?=</span>)
上述的表達(dá)式就是說明,我現(xiàn)在斷言整數(shù)\d+的 后面 能 匹配表達(dá)式:</span>
讓我們來驗(yàn)證下結(jié)果:
相應(yīng)的正向后行斷言可以這樣寫表達(dá)式:
(?<=閱讀數(shù):)\d+
上述的表達(dá)式就是說明,我現(xiàn)在斷言整數(shù) \d+ 的 前面 能 匹配表達(dá)式: 閱讀數(shù):
驗(yàn)證下結(jié)果如下:
分組
正則表達(dá)式中用小括號(hào) () 來做分組,也就是括號(hào)中的內(nèi)容作為一個(gè)整體。
因此當(dāng)我們要匹配分組 he 的時(shí)候,可以用下面這個(gè)表達(dá)式 :
(he)
我們看到正則表達(dá)式用小括號(hào)來做分組,那么問題來了:
如果要匹配的字符串中本身就包含小括號(hào),那應(yīng)該怎么辦?
針對(duì)這種情況,正則提供了轉(zhuǎn)義的方式,也就是要把這些元字符、限定符或者關(guān)鍵字轉(zhuǎn)義成普通的字符,做法很簡(jiǎn)單,就是在要轉(zhuǎn)義的字符前面加個(gè)斜杠()即可。
因此當(dāng)我們要匹配分組 (he) 的時(shí)候,可以用下面這個(gè)表達(dá)式 :
(\(he\))
下面我們用一個(gè)正則表達(dá)式的圖形生成工具,做一個(gè)對(duì)比的實(shí)驗(yàn),讓我們對(duì)分組和定位有個(gè)了解。
1:匹配 he 分組一次 ;
2:匹配 he 分組零或多次;
3:匹配以 he 開頭的分組一次;
4:匹配以 he 開頭的分組零或多次
捕獲與反向引用
單純說到捕獲,他的意思是匹配表達(dá)式,但捕獲通常和分組聯(lián)系在一起,也就是“捕獲組”。
捕獲組:
匹配子表達(dá)式的內(nèi)容,把匹配結(jié)果保存到內(nèi)存中以數(shù)字編號(hào)或顯示命名的組里(可以把它想象為java中的array和map),以深度優(yōu)先進(jìn)行編號(hào),之后可以通過序號(hào)或名稱來使用這些匹配結(jié)果。
捕獲組的表達(dá)式為: (exp) ,這個(gè)語法跟上面講到的分組的概念是一樣的,只是捕獲將匹配到的分組,保存在了內(nèi)存中,留待后面使用。具體怎么時(shí)候他不管,他只需要把匹配到的分組保存在內(nèi)存中就可以了。
有一種情況當(dāng)在匹配的過程中,需要與已經(jīng)捕獲到的分組進(jìn)行匹配,這時(shí)就需要使用到保存在內(nèi)存中的捕獲組了,這種使用方式就被稱為: 反向引用 。
假設(shè)我有這樣一段文字:
aa12bb23cc34
現(xiàn)在我想拿到成對(duì)的字符,該怎么做呢?這種情況下通過斷言或者其他方式是辦不到的,那我們能否在匹配的過程中將匹配到的一個(gè)字符先保存在內(nèi)存中,然后匹配下一個(gè)字符時(shí)再與上一個(gè)字符相比較,如果相等,就說明匹配成了,拿到了成對(duì)的字符了。
那首先我們先要寫一個(gè)匹配單個(gè)字符分組的表達(dá)式:
(\w)
那當(dāng)匹配時(shí)捕獲到一個(gè)字符分組時(shí),我們需要將該字符引用出來,與下一個(gè)字符想比較,我們期望匹配的下一個(gè)字符也與我當(dāng)前保存的字符相等,那么表達(dá)式就變成了這樣:
(\w)\1
這里的 \1 表示的是,當(dāng)前正則表達(dá)式匹配到的 第1個(gè) 分組,那就意味著, \2 表示 第2個(gè)分組。
做個(gè)測(cè)試,結(jié)果如下:
那如果我想再匹配復(fù)雜一點(diǎn)的結(jié)果,比如:XYY 這種的結(jié)果,又該怎么寫呢?
其實(shí)有了上面的基礎(chǔ)之后就很簡(jiǎn)單了,我們需要做的就是 對(duì)捕獲到的第2個(gè)分組進(jìn)行反向引用就可以了!
具體的表達(dá)式為:
(\w)(\w)\2
測(cè)試結(jié)果如下:
表示成圖形就是這樣:
附常用工具:
在線正則測(cè)試:http://tool.oschina.net/regex/
生成正則圖片:https://regexper.com