JavaScript - 正則表達式

1、正則表達式概述
ECMAScript 3 開始支持正則表達式革答,其語法和 Perl 語法很類似溪食,一個完整的正則表達式結(jié)構(gòu)如下:
var expression = / pattern / flags ;
其中囊卜,模式(pattern)部分可以是任何簡單或復(fù)雜的正則表達式,可以包含字符類错沃、限定符栅组、分組、向前查找以及反向引用枢析。

每個正則表達式都可帶有一或多個標志(flags)玉掸,用以標明正則表達式的行為,正則表達式支持下列 3 個標志:

g: 表示全局(global)模式醒叁,即模式將被應(yīng)用于所有字符串司浪,而非在發(fā)現(xiàn)第一個匹配項時立即停止;
i : 表示不區(qū)分大小寫(case-insensitive)模式辐益,即在確定匹配項時忽略模式與字符串的大小寫;
m:表示多行(multiline)模式脱吱,即在到達一行文本末尾時還會繼續(xù)查找下一行中是否存在與模式匹配的項智政。
如果多個標志同時使用時,則寫成:gmi箱蝠。

正則表達式的創(chuàng)建有兩種方式:new RegExp(expression)和 直接字面量续捂。
//使用直接字面量創(chuàng)建
var exp1 = /(^\s+)|(\s+$)/g;

//使用RegExp對象創(chuàng)建
var exp2 = new RegExp("(^\\s+)|(\\s+$)","g");
exp1exp2 是兩個完全等價的正則表達式,需要注意的是宦搬,傳遞給 RegExp構(gòu)造函數(shù)的兩個參數(shù)都是字符串牙瓢,不能把正則表達式字面量傳遞給 RegExp 構(gòu)造函數(shù)。

與其他語言中的正則表達式類似间校,模式中使用的所有元字符都必須轉(zhuǎn)義矾克。正則表達式中的元字符包括:
( [ { \ ^ $ | ) ? * + .] }
這些元字符在正則表達式中都有一或多種特殊用途,因此如果想要匹配字符串中包含的這些字符憔足,就必須對它們進行轉(zhuǎn)義胁附。

//匹配 .docx
var exp = /\.docx/gi ;

由于 RegExp 構(gòu)造函數(shù)的模式參數(shù)是字符串酒繁,所以在某些情況下要對字符進行雙重轉(zhuǎn)義。所有元字符都必須雙重轉(zhuǎn)義控妻,那些已經(jīng)轉(zhuǎn)義過的字符也是如此州袒。

// 對 \. 再次轉(zhuǎn)義
var exp = new RegExp("\\.docx","gi");

//匹配 \n
var exp1 = /\\n/g; //對\n中的\轉(zhuǎn)義
var exp2 = new RegExp("\\\\n","g"); // 對 \\n 再次轉(zhuǎn)義

2、() [] {} 的區(qū)別

() 的作用是提取匹配的字符串弓候。表達式中有幾個()就會得到幾個相應(yīng)的匹配字符串郎哭。比如 (\s+) 表示連續(xù)空格的字符串。

[]是定義匹配的字符范圍菇存。比如 [a-zA-Z0-9]表示字符文本要匹配英文字符和數(shù)字夸研。

{}一般用來表示匹配的長度,比如 \d{3}表示匹配三個數(shù)字撰筷,\d{1,3} 表示匹配1~3個數(shù)字陈惰,\d{3,}表示匹配3個以上數(shù)字。

3毕籽、^$

^ 匹配一個字符串的開頭抬闯,比如 (^a)就是匹配以字母a開頭的字符串

$匹配一個字符串的結(jié)尾,比如 (b$)就是匹配以字母b結(jié)尾的字符串

^ 還有另個一個作用就是取反,比如[^xyz] 表示匹配的字符串不包含xyz

注意問題:

如果 ^出現(xiàn)在[ ]中一般表示取反关筒,而出現(xiàn)在其他地方則是匹配字符串的開頭溶握。
^$ 配合可以有效匹配完整字符串:/d+/.test('4xpt') -> true,而 /^\d+$/.test('4xpt')->false

4蒸播、\d \s \w .

\d 匹配一個非負整數(shù)睡榆, 等價于[0-9]

\s匹配一個空白字符

\w 匹配一個英文字母或數(shù)字,等價于[0-9a-zA-Z]

. 匹配除換行符以外的任意字符袍榆,等價于[^\n]

5胀屿、* + ?

*表示匹配前面元素0次或多次,比如(\s*)就是匹配0個或多個空格

+ 表示匹配前面元素1次或多次包雀,比如 (\d+)就是匹配由至少1個整數(shù)組成的字符串

?表示匹配前面元素0次或1次宿崭,相當于{0,1} ,比如(\w?) 就是匹配最多由1個字母或數(shù)字組成的字符串

6才写、$1\1

$1-$9存放著正則表達式中最近的9個正則表達式的提取的結(jié)果葡兑,這些結(jié)果按照子匹配的出現(xiàn)順序依次排列≡薏荩基本語法是:RegExp.$n 讹堤,這些屬性是靜態(tài)的,除了replace中的第二個參數(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表示后向引用,是指在正則表達式中,從左往右數(shù)岖沛,第1個()中的內(nèi)容暑始,以此類推,\2表示第2個()婴削,\0表示整個表達式廊镜。

//匹配日期格式,表達式中的\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只能用在表達式中唉俗,而$n只能用在表達式之外的地方嗤朴。

7、testmatch

前面的大都是JS正則表達式的語法虫溜,而test則是用來檢測字符串是否匹配某一個正則表達式雹姊,如果匹配就會返回true,反之則返回false

/\d+/.test("123") ; //true
 
/\d+/.test("abc") ; //false

match是獲取正則匹配到的結(jié)果,以數(shù)組的形式返回

"186a619b28".match(/\d+/g); // ["186","619","28"]

8衡楞、replace

replace 本身是JavaScript字符串對象的一個方法吱雏,它允許接收兩個參數(shù):

replace([RegExp|String],[String|Function])
第1個參數(shù)可以是一個普通的字符串或是一個正則表達式
第2個參數(shù)可以是一個普通的字符串或是一個回調(diào)函數(shù)

如果第1個參數(shù)是 RegExp,JS會先提取RegExp匹配出的結(jié)果瘾境,然后用第2個參數(shù)逐一替換匹配出的結(jié)果

如果第2個參數(shù)是回調(diào)函數(shù)歧杏,每匹配到一個結(jié)果就回調(diào)一次,每次回調(diào)都會傳遞以下參數(shù):
result: 本次匹配到的結(jié)果

$1,...$9: 正則表達式中有幾個()迷守,就會傳遞幾個參數(shù)犬绒,$1~$9分別代表本次匹配中每個()提取的結(jié)果,最多9個

offset:記錄本次匹配的開始位置
source:接受匹配的原始字符串

9兑凿、經(jīng)典案例

(1) 實現(xiàn)字符串的trim函數(shù)凯力,去除字符串兩邊的空格。

String.prototype.trim = function(){
    //方式一:將匹配到的每一個結(jié)果都用""替換
    return this.replace(/(^\s+)|(\s+$)/g,function(){
        return "";
    });
    //方式二:和方式一的原理相同
    return this.replace(/(^\s+)|(\s+$)/g,'');
};

^\s+ 表示以空格開頭的連續(xù)空白字符礼华,\s+$ 表示以空格結(jié)尾的連續(xù)空白字符咐鹤,加上()就是將匹配到的結(jié)果提取出來,由于是 | 的關(guān)系圣絮,因此這個表達式最多會match到兩個結(jié)果集祈惶,然后執(zhí)行兩次替換:

String.prototype.trim = function(){
    /**
     * @param rs:匹配結(jié)果
     * @param $1:第1個()提取結(jié)果
     * @param $2:第2個()提取結(jié)果
     * @param offset:匹配開始位置
     * @param source:原始字符串
     */
    this.replace(/(^\s+)|(\s+$)/g,function(rs,$1,$2,offset,source){
        //arguments中的每個元素對應(yīng)一個參數(shù)
        console.log(arguments);
    });
};
 
" abcd ".trim();

輸出結(jié)果:

[" ", " ", undefined, 0, " abcd "] //第1次匹配結(jié)果
[" ", undefined, " ", 5, " abcd "] //第2次匹配結(jié)果

(2) 提取瀏覽器 url 中的參數(shù)名和參數(shù)值,生成一個key/value 的對象晨雳。

function getUrlParamObj(){
    var obj = {};
    //獲取url的參數(shù)部分
    var params = window.location.search.substr(1);
    //[^&=]+ 表示不含&或=的連續(xù)字符行瑞,加上()就是提取對應(yīng)字符串
    params.replace(/([^&=]+)=([^&=]*)/gi,function(rs,$1,$2){
        obj[$1] =  decodeURIComponent($2);
    });
 
    return obj;
}

/([^&=]+)=([^&=]*)/gi 每次匹配到的都是一個完整key/value,形如xxxx=xxx, 每當匹配到一個這樣的結(jié)果時就執(zhí)行回調(diào)奸腺,并傳遞匹配到的keyvalue餐禁,對應(yīng)到$1$2

(3) 擴展 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是正則表達式中第一個()中匹配的內(nèi)容。需要注意的是,replace的第二個參數(shù)只能是字符串或函數(shù)末盔,因此筑舅,這里的$1需要放在引號中。

(4) 在字符串指定位置插入新字符串陨舱。

String.prototype.insetAt = function(str,offset){
    offset = offset + 1;
    //使用RegExp()構(gòu)造函數(shù)創(chuàng)建正則表達式
    var regx = new RegExp("(^.{"+offset+"})");
    return this.replace(regx,"$1"+str);
};
 
"abcd".insetAt('xyz',2); //在c字符后插入xyz
>> "abcxyzd"

offset=2時翠拣,正則表達式為:(^.{3}) .表示除\n之外的任意字符,{3}表示匹配前三個連續(xù)字符游盲,加()就會將匹配到的結(jié)果提取出來误墓,然后通過replace將匹配到的結(jié)果替換為新的字符串,形如:結(jié)果=結(jié)果+str

(5) 將手機號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})可以匹配完整的手機號谜慌,并分別提取前 3 位、4-7 位和 8-11位莺奔,"1****3" 是將第 2 個匹配結(jié)果用****代替并組成新的字符串欣范,然后替換完整的手機號。

(6) 實現(xiàn)HTML編碼令哟,將< / > " & `等字符進行轉(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 "'"
        }
    });
}

另:常用正則表達式

//將數(shù)轉(zhuǎn)為帶,號的貨幣形式
'1231232341'.replace(/\B(?=(\d{3})+(?!\d))/g,',');
'1231232341'.replace(/(\d)(?=(?:\d{3})+$)/g,'$1,');

//去掉括號及其內(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ī)則列出來如下表所示:

image

要點

貪與不貪

舉個例子励饵,假設(shè)有以下這段html字符驳癌,我想拿到a標簽中的內(nèi)容:

  1. <a>南京長江大橋</a>哈哈<a>南京市長江大橋</a>

然后我寫了這樣一個正則: <a>(.)*</a>

在線測試的結(jié)果如下:

image

這個結(jié)果與我們的預(yù)期不符,正常我應(yīng)該得到兩個匹配的結(jié)果才對役听,但是現(xiàn)在卻只匹配到一個結(jié)果颓鲜。

現(xiàn)在把剛剛的正則改成這樣: <a>(.)*?</a>

在線測試的結(jié)果如下:

image

說的是正則在不約束的情況下會繼續(xù)自動向右進行匹配,直到匹配結(jié)束典予,只要匹配的數(shù)據(jù)與正則的最后一個值匹配就算是匹配到了甜滨。

不貪 說的是只要匹配到就結(jié)束,不繼續(xù)向右進行匹配了瘤袖。

問號 ? 就解決了貪婪的問題衣摩,使得問號前面的字符匹配到之后就結(jié)束,但是并不是把 ? 放在哪里都可以解決貪婪的捂敌,在正則里艾扮,有一些屬于貪婪模式量詞,比如以下這些:

  1. {m,n}
  2. {m,}
  3. ?
  4. *
  5. +

斷言與零寬

在java中我們知道 斷言 可以用來聲明一個應(yīng)該為 true 的事實占婉,只有當斷言為真時才會繼續(xù)進行后續(xù)的操作泡嘴。

在正則中也有 斷言 的概念,但是在正則中除了 斷言 還有 零寬 的概念逆济。

  • 斷言

通俗點將斷言就是 “我斷定某某情況是真的” 酌予,而正則中的斷言磺箕,就是說正則可以斷定在 指定的內(nèi)容前面后面 會出現(xiàn)滿足指定規(guī)則的內(nèi)容。比如 "aa1bb2cc3"抛虫,正則可以用斷言找出 bb2 前面有 aa1松靡,也可以找出 bb2 后面有 cc3。

  • 零寬:

零寬就是沒有寬度建椰,在正則中雕欺,斷言只是匹配位置,不占字符棉姐,也就是說阅茶,匹配結(jié)果里是不會返回斷言本身的。

斷言一共有四種情況:

image

讓我們來舉個例子來說明吧谅海,假設(shè)我們現(xiàn)在拿到了某個網(wǎng)頁的html脸哀,里面有個閱讀數(shù)的標簽:

  1. <span class="read-cnt">閱讀數(shù):1024</span>
    現(xiàn)在我們要獲取到這個閱讀數(shù),該怎么辦呢扭吁?

如果用正向先行斷言來匹配的話撞蜂,可以這樣來寫:

  1. \d+(?=</span>)
    上述的表達式就是說明,我現(xiàn)在斷言整數(shù) \d+后面 匹配表達式: </span>

讓我們來驗證下結(jié)果:

image

相應(yīng)的正向后行斷言可以這樣寫表達式:
(?<=閱讀數(shù):)\d+

上述的表達式就是說明侥袜,我現(xiàn)在斷言整數(shù) \d+前面 匹配表達式: 閱讀數(shù):

驗證下結(jié)果如下:

image

分組

正則表達式中用小括號 () 來做分組蝌诡,也就是括號中的內(nèi)容作為一個整體咆课。

因此當我們要匹配分組 he 的時候譬圣,可以用下面這個表達式 :
(he)

image

我們看到正則表達式用小括號來做分組,那么問題來了:

如果要匹配的字符串中本身就包含小括號能庆,那應(yīng)該怎么辦九杂?

針對這種情況颁湖,正則提供了轉(zhuǎn)義的方式,也就是要把這些元字符例隆、限定符或者關(guān)鍵字轉(zhuǎn)義成普通的字符甥捺,做法很簡單,就是在要轉(zhuǎn)義的字符前面加個斜杠()即可镀层。

因此當我們要匹配分組 (he) 的時候镰禾,可以用下面這個表達式 :
(\(he\))

image

下面我們用一個正則表達式的圖形生成工具,做一個對比的實驗唱逢,讓我們對分組和定位有個了解吴侦。

1:匹配 he 分組一次 ;

image

2:匹配 he 分組零或多次;

image

3:匹配以 he 開頭的分組一次坞古;

image

4:匹配以 he 開頭的分組零或多次

image

捕獲與反向引用

單純說到捕獲备韧,他的意思是匹配表達式,但捕獲通常和分組聯(lián)系在一起绸贡,也就是“捕獲組”盯蝴。

捕獲組:

匹配子表達式的內(nèi)容,把匹配結(jié)果保存到內(nèi)存中以數(shù)字編號或顯示命名的組里(可以把它想象為java中的array和map)听怕,以深度優(yōu)先進行編號捧挺,之后可以通過序號或名稱來使用這些匹配結(jié)果。

捕獲組的表達式為: (exp) 尿瞭,這個語法跟上面講到的分組的概念是一樣的闽烙,只是捕獲將匹配到的分組,保存在了內(nèi)存中声搁,留待后面使用黑竞。具體怎么時候他不管,他只需要把匹配到的分組保存在內(nèi)存中就可以了疏旨。

有一種情況當在匹配的過程中很魂,需要與已經(jīng)捕獲到的分組進行匹配,這時就需要使用到保存在內(nèi)存中的捕獲組了檐涝,這種使用方式就被稱為: 反向引用 遏匆。

假設(shè)我有這樣一段文字:
aa12bb23cc34
現(xiàn)在我想拿到成對的字符,該怎么做呢谁榜?這種情況下通過斷言或者其他方式是辦不到的幅聘,那我們能否在匹配的過程中將匹配到的一個字符先保存在內(nèi)存中,然后匹配下一個字符時再與上一個字符相比較窃植,如果相等帝蒿,就說明匹配成了,拿到了成對的字符了巷怜。

那首先我們先要寫一個匹配單個字符分組的表達式:
(\w)
那當匹配時捕獲到一個字符分組時葛超,我們需要將該字符引用出來,與下一個字符想比較延塑,我們期望匹配的下一個字符也與我當前保存的字符相等巩掺,那么表達式就變成了這樣:
(\w)\1
這里的 \1 表示的是,當前正則表達式匹配到的 第1個 分組页畦,那就意味著胖替, \2 表示 第2個分組。

做個測試豫缨,結(jié)果如下:

image

那如果我想再匹配復(fù)雜一點的結(jié)果独令,比如:XYY 這種的結(jié)果,又該怎么寫呢好芭?

其實有了上面的基礎(chǔ)之后就很簡單了燃箭,我們需要做的就是 對捕獲到的第2個分組進行反向引用就可以了!

具體的表達式為:
(\w)(\w)\2
測試結(jié)果如下:

image

表示成圖形就是這樣:

image

附常用工具:

在線正則測試:http://tool.oschina.net/regex/
生成正則圖片:https://regexper.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舍败,一起剝皮案震驚了整個濱河市招狸,隨后出現(xiàn)的幾起案子敬拓,更是在濱河造成了極大的恐慌,老刑警劉巖裙戏,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乘凸,死亡現(xiàn)場離奇詭異,居然都是意外死亡累榜,警方通過查閱死者的電腦和手機营勤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來壹罚,“玉大人葛作,你說我怎么就攤上這事〔荩” “怎么了赂蠢?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辨泳。 經(jīng)常有香客問我客年,道長,這世上最難降的妖魔是什么漠吻? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任量瓜,我火速辦了婚禮,結(jié)果婚禮上途乃,老公的妹妹穿的比我還像新娘绍傲。我一直安慰自己,他們只是感情好耍共,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布烫饼。 她就那樣靜靜地躺著,像睡著了一般试读。 火紅的嫁衣襯著肌膚如雪杠纵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天钩骇,我揣著相機與錄音比藻,去河邊找鬼。 笑死倘屹,一個胖子當著我的面吹牛银亲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纽匙,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼务蝠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烛缔?” 一聲冷哼從身側(cè)響起馏段,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤轩拨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后院喜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亡蓉,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年够坐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片崖面。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡元咙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出巫员,到底是詐尸還是另有隱情庶香,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布简识,位于F島的核電站赶掖,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏七扰。R本人自食惡果不足惜奢赂,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望颈走。 院中可真熱鬧膳灶,春花似錦、人聲如沸立由。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锐膜。三九已至毕箍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間道盏,已是汗流浹背而柑。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荷逞,地道東北人牺堰。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像颅围,于是被迫代替她去往敵國和親伟葫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容