正則表達(dá)式

正則表達(dá)式(Regular Expression,簡稱regex)作為一種工具,它擅長的領(lǐng)域是文本處理。最基本的用途是查找(search/match)和替換(replace)特定的文本內(nèi)容,在此之上可以延伸到:

  • 驗(yàn)證(validation)內(nèi)容是否符合指定的規(guī)則

  • 提取(Extract)指定的內(nèi)容

1 JavaScript中正則表達(dá)式

JavaScript中為正則表達(dá)式提供了專門的RegExp類型,可以使用new RegExp()實(shí)例化一個(gè)正則表達(dá)式,不過通常我們使用如下的語法創(chuàng)建一個(gè)正則表達(dá)式。

let expression = /pattern/flags;

/pattern/表示正則表達(dá)式的匹配模式,flags表示該表達(dá)式支持的標(biāo)志。

1.1 正則表達(dá)式的標(biāo)志

一個(gè)正則表達(dá)式可以帶一個(gè)或多個(gè)flag,flag有如下取值:

  • g 表示全局模式(global),默認(rèn)情況下,js的正則表達(dá)式在找到第一個(gè)匹配項(xiàng)時(shí),就會停止匹配,加上g標(biāo)志后,表達(dá)式會查找目標(biāo)字符串中所有的匹配項(xiàng)。
  • i 忽略大小寫
  • m 多行模式

flag可以組合使用,比如/exp/gi表示exp表達(dá)式用于全局匹配,且忽略大小寫。

1.2 正則表達(dá)式的用法

我們先不看正則表達(dá)式是怎么來的,先看它是怎么用的。使用RegExp主要是其實(shí)例方法以及外部支持RegExp的方法。后續(xù)的章節(jié)中,我們以patt表示表達(dá)式,text表示目標(biāo)字符串。

  • RegExp.exec()方法,exec是RegExp的實(shí)例方法,用法是patt.exec(text),在text中查找匹配patt的內(nèi)容,并以數(shù)組的方式返回第一個(gè)匹配的且未返回的結(jié)果,當(dāng)無匹配時(shí)返回null。注意前面這句話中的"第一個(gè)"和"未返回的"我打了重點(diǎn)符號,
let text="112233";
let patt=/(\d)\1/g;

let matchs = patt.exec(text);
//["11", "1", index: 0, input: "112233", groups: undefined]

matchs = patt.exec(text);
//["22", "2", index: 2, input: "112233", groups: undefined]

matchs = patt.exec(text);
//["33", "3", index: 4, input: "112233", groups: undefined]

matchs = patt.exec(text);
//null

本列中/(\d)\1/g表達(dá)式用來查找連續(xù)相同的數(shù)字,因?yàn)檫€沒有講表達(dá)式,所以這里不需要知道表達(dá)式的細(xì)節(jié)。patt.exec(text)查找text中匹配patt表達(dá)式的內(nèi)容,第一次返回了一個(gè)數(shù)組并賦值給matchs,這個(gè)數(shù)組有index和input屬性,index表示當(dāng)前匹配的內(nèi)容在text中的起點(diǎn)位置,input表示目標(biāo)字符串(及text文本內(nèi)容)。matchs[0]表示當(dāng)前匹配的內(nèi)容,這里匹配的內(nèi)容是11,如果表達(dá)式產(chǎn)生了分組(還沒講分組),會在matchs中添加分組內(nèi)容,這里是1就是分組內(nèi)容。

再次執(zhí)行exec方法,返回第二個(gè)匹配的內(nèi)容數(shù)組,直到返回為null,表示所有的匹配內(nèi)容都已提取完。

如果表達(dá)式?jīng)]有設(shè)置全局模式g,那么每次執(zhí)行patt.exec(text)時(shí),都會從頭開始匹配,及每一次執(zhí)行exec()方法就會返回第一次的內(nèi)容。

  • RegExp.test()方法用于測試patt與text是否匹配,它只會返回目標(biāo)字符串是否與表達(dá)式匹配,不會返回文本內(nèi)容,這個(gè)方法一般用于驗(yàn)證用戶的輸入。

    let patt = /^d{3}-d{8}$/;
    let phoneNumber = "010-88888888";
    
    let flag = patt.test(phoneNumber);
    //true
    

    上例中,patt表達(dá)式用于驗(yàn)證電話號碼的格式,而使用test方法,只返回目標(biāo)字符串是否為匹配,而不返回具體的內(nèi)容。

  • string.match()方法是string的實(shí)例方法,與RegExp的方法類似,返回匹配的字符集合,注意,這里不包含組信息。

    let text="11112223455";
    let patt=/(\d)\1/g;
    
    text.match(patt);
    // ["11", "11", "22", "55"]
    
  • string.search()方法,返回第一個(gè)匹配的字符串的位置,如果沒有匹配的內(nèi)容,返回-1。

    let text="12345";
    let patt=/(\d)\1/g;
    
    text.search(patt);
    //-1 沒有任何匹配的內(nèi)容
    
    text = "1233445";
    text.search(patt);
    //2 返回字符串的第一個(gè)匹配內(nèi)容的位置,這里是33,而后面44的位置不會返回
    
  • string.replace()方法是string的實(shí)例方法,用于替換指定的字符串,返回一個(gè)替換完成的字符串,相對之前的方法要復(fù)雜些,它接收兩個(gè)參數(shù):

    • 第一個(gè)參數(shù)時(shí)要匹配的內(nèi)容,是一個(gè)RegExp對象或字符串。
    • 第二個(gè)參數(shù)要替換的內(nèi)容,可以是字符串或函數(shù)。

    當(dāng)?shù)谝粋€(gè)參數(shù)是字符串時(shí),只能替換第一個(gè)匹配的內(nèi)容,如果要全局替換,需要使用全局模式的正則表達(dá)式。

    let text = "cat,bat,sat,fat";
    let tmp = text.replace("at","ond");
    //text "cat,bat,sat,fat"
    //tmp "cond,bat,sat,fat"
    

    第一個(gè)參數(shù)使用字符串時(shí),只能替換第一個(gè)匹配的內(nèi)容,后面的bat并沒有被替換。

    let text = "cat,bat,sat,fat";
    let tmp = text.replace(/at/g,"ond");
    // tmp "cond,bond,sond,fond"
    

    第一個(gè)參數(shù)使用帶全局標(biāo)志(g)的正則表達(dá)式,會替換全部的匹配的內(nèi)容。替換內(nèi)容還可以使用特殊的字符(如:$n,這個(gè)叫回溯引用),引用前面匹配的結(jié)果。

    let text = "01088888888,01077777777,01066666666";
    let patt = /(\d{3})(\d{8})/g
    let tmp = text.replace(patt,"$1-$2");
    //tmp "010-88888888,010-77777777,010-66666666"
    

    上面使用特殊字符引$n引用了匹配結(jié)果的分組信息,并將匹配的結(jié)果格式化為自定的樣式。$1引用了第一個(gè)分組信息(\d{3}),$2引用了第二個(gè)分組信息(\d{8})

    replace方法的第二個(gè)參數(shù)是Function時(shí),replace會向該函數(shù)傳遞三個(gè)參數(shù),分別是,匹配的內(nèi)容,匹配項(xiàng)的位置,原生字符串。使用function可以做更精細(xì)的替換,這里不深入講解。

  • string.split()方法,該方法用指定的分隔符將指定的字符串分隔為多個(gè)子字符串。這個(gè)分隔符可以是字符串也可以是RegExp對象。使用RegExp作為分隔符時(shí),該方法在不同瀏覽器存在差異。

2 編寫正則表達(dá)式

正則表達(dá)式的符號較多,可分為普通字符,轉(zhuǎn)義字符,特殊符號,定位符,限定符與組共6大類。

2.1 普通字符

普通字符代表字符本身,如:

let text = "cat,bat";

let patt = /at/g;
let tmp = text.match(patt);
//tmp ["at", "at"]

目標(biāo)字符串text,會找到兩處與patt相關(guān)的內(nèi)容。

2.2 轉(zhuǎn)義字符

有些普通字符轉(zhuǎn)義后代表特殊的含義,比如:w在轉(zhuǎn)義后\w表示任意的0-9,a-z,A-Z以及_下劃線。

轉(zhuǎn)義字符 說明
\w 匹配任一0-9,a-z,A-Z,_字符
\W 匹配任一非0-9,a-z,A-Z,_的字符
\d 匹配任一0-9的數(shù)字
\D 匹配任一非0-9的數(shù)字
\s 匹配任一空格,tab和換行符\n
\S 匹配任一非空格,tab和換行符\n的字符

轉(zhuǎn)義字符有很多,這里我只列出了常用的3個(gè)w、d、s,他們的大寫是對應(yīng)的含義取反。

let text = "要出差3天,還是1周";
let patt = /\d天/g;

let tmp = patt.exec(text);
// ["3天", index: 3, input: "要出差3天,還是1周", groups: undefined]

上面的例子使用\d表示匹配數(shù)字,后面跟普通字符"天",這樣就能從上面的一段文字中提取出幾天,注意:雖然1也是數(shù)字,但是它不符合表達(dá)式,因?yàn)楸磉_(dá)式是數(shù)據(jù)后面跟著"天"。

2.3 特殊符號

普通字符代表字符本身,有些普通字符通過轉(zhuǎn)義代表了另一類含義。而有些字符不需要轉(zhuǎn)義就代表了與本身不同的含義,如果要表示這些字符本身的含義,還得通過轉(zhuǎn)義符來轉(zhuǎn)義。這些字符就是我們說的特殊字符:

特殊字符 說明
\ 轉(zhuǎn)義符,如果要表示\本身,還得通過轉(zhuǎn)義符,寫成\\,后面的特殊字符同理,不在累述
. 表示任意字符,但不包括換行符
| 表示或
[ ] 表示匹配中括號中的任意一個(gè)字符,如[ab],表示匹配a或b,與|的含義相同
[^ ] 表示不能匹配中括號中的任意一個(gè)字符

.表示任意字符,但不包括換行符,如果我們要代表任意字符可以使用前面的\s\S的組合[\s\S]。

2.4 定位符

定位符比較特別,前面我們講的不管是普通字符、還是特殊字符它匹配的要么是單個(gè)字符,要么就是字符集中的某個(gè)字符,但終歸說來就是匹配的字符,而定位符卻是匹配的位置,注意是位置而不是字符。如果要表示字符本身,也需要使用轉(zhuǎn)義符。

定位符 說明
^ 匹配字符串開始位置,注意^[^ ]中的^不同,[^ ]中^與[ 是一個(gè)整體的,表示非的意思
$ 匹配字符串的結(jié)束位置
\b 表示單詞的邊界
\B 表示非單詞邊界

^$表示字符串的開始和結(jié)束位置,這個(gè)很好理解。而\b表示單詞的邊界,我們知道單詞是以空格隔開的,那么單詞的邊界就是一個(gè)單詞的開始或結(jié)尾處,\B表示非單詞的邊界,即非單詞的開始或結(jié)尾處。例如:

let text = "element release bele";
let patt = /ele/g;

let tmp = patt.exec(text);
//["ele", index: 0, input: "element release", groups: undefined]
tmp = patt.exec(text);
//["ele", index: 9, input: "element release", groups: undefined]
tmp = patt.exec(text);
//["ele", index: 17, input: "element release bele", groups: undefined]
tmp = patt.exec(text);
//null

通過普通字符能夠找到3個(gè)匹配的字符,位置分別在0,9和17。下面我們看看通過\b\B查找。

let text = "element release bele";

//使用\b定位在單詞的開始位置
let patt = /\bele/g;
patt.exec(text);
//["ele", index: 0, input: "element release", groups: undefined]


//使用\B定位ele元素不在開始或結(jié)束的任意位置
patt = /\Bele/g;
patt.exec(text);
//["ele", index: 9, input: "element release", groups: undefined]

//使用\b定位在單詞的結(jié)束位置
patt = /ele\b/g;
tmp = patt.exec(text);
//["ele", index: 17, input: "element release bele", groups: undefined]

使用\B能找到不是以ele開頭或結(jié)尾的內(nèi)容,\b表示單詞的邊界,邊界分為開始處和結(jié)尾處,\b如果加在前面代表匹配單詞的開始處\bele,加在后面表示匹配單詞的結(jié)尾處ele\b

2.5 限定符

限定符表示重復(fù)的次數(shù),與其它的類搭配使用。如果要表示字符本身的含義,也需要使用轉(zhuǎn)義符

限定符 說明
+ 表示+前面的內(nèi)容,重復(fù)1次到N次
* 表示*前面的內(nèi)容,重復(fù)0次到N次
? 表示?前面的內(nèi)容,重復(fù)0次或1次
{n} 表示{}前面的內(nèi)容,重復(fù)n次
{n,m} 表示{}前面的內(nèi)容,重復(fù)n次到m次
{n,} 表示{}前面的內(nèi)容,至少重復(fù)n次
? 限定符之后,表示懶惰模式

上面出現(xiàn)了兩個(gè)?號,表示?有2種用法,一種是在字符或分組之后,表示重復(fù)0次或1次。第2中是在限定符之后,表示懶惰模式。

通常正則表達(dá)式在加了限定符后,默認(rèn)都會匹配盡可能多的字符,這種叫做貪婪模式,比如:

let text = "gooooogle;

// 貪婪模式
let patt = /o+/g;
text.match(patt);
//["ooooo"]

// 懶惰模式
let patt = /o+?/g;
text.match(patt);
//["o", "o", "o", "o", "o"]

這里的限定符是+,表示前面的o可以重復(fù)1-n次,在貪婪模式下,表達(dá)式會盡可能多的匹配,對應(yīng)到本例就是5個(gè)連續(xù)的o,所以,返回了一個(gè)"ooooo"字符串。而在懶惰模式下,表達(dá)式會匹配盡可能少的字符,對應(yīng)到本例就是當(dāng)匹配了一個(gè)o時(shí),表達(dá)式就返回了,因?yàn)楸磉_(dá)式是全局匹配,所以返回了5次o。

2.6 分組

分組使用()來表示,它將括號內(nèi)的表達(dá)式看做一個(gè)整體??梢詫⒎纸M內(nèi)的表達(dá)式看做整個(gè)表達(dá)式的一個(gè)子表達(dá)式。子表達(dá)式還可以嵌套子表達(dá)式。

2.6.1 分組的限定

我們前面做限定(如+,*,?,{n,m})時(shí),都是針對限定符前面的單個(gè)字符,比如https?表示的是對s這個(gè)字符重復(fù)0或1次,代表的是http和https,分組的一個(gè)用途是將字符組成一個(gè)整體,這樣就可以對這個(gè)整體(分組)做限定。

let text = "haha"

let patt = /haha/g
let tmp = text.match(patt);
// ["haha"]

patt = /(ha)+/g
tmp = text.match(patt);
// ["haha"]

/(ha)+/g是對ha分組做+限定。

2.6.2 回溯引用

回溯引用的含義是,在同一個(gè)表達(dá)式中,后面的部分可以用\n的方式引用前面分組匹配的結(jié)果。正則表達(dá)式會為每個(gè)分組從1開始順序的分配一個(gè)編號。結(jié)合前面的\n方式,當(dāng)n=1,表示第一個(gè)分組的內(nèi)容,以此類推。比如我們要查找html文檔中所有的h標(biāo)簽,可以使用如下表達(dá)式:

let patt = /<(h[1-6])[\S\s]*?<\/\1>/ig

/<(h[1-6])[\S\s]*?<\/\1>/ig表達(dá)式使用全局標(biāo)記g和忽略大小寫標(biāo)記i,<是普通字符,(h[1-6])作為一個(gè)分組,能匹配h1-h6的內(nèi)容,這個(gè)分組的編號為1,表達(dá)式的后面部分使用\1引用這個(gè)分組的內(nèi)容,[\S\s]*表示任一空格和非空格字符重復(fù)0到n次,及表示的是任意行數(shù)的全部內(nèi)容,注意.*表示的是單行內(nèi)容,因?yàn)?code>.不包含換行符,所以重復(fù)n次,也只能是一行的內(nèi)容,這里的加載限定符之后,表示懶惰模式。

回溯引用還可以用于高級的替換場景,這個(gè)需要結(jié)合具體的腳本語言來實(shí)現(xiàn),下面以js為例

let text = "<p>如有需要,請發(fā)郵件到 xxx@gmail.com </p>";
let patt = /(\b\w+[\w\.]*@[\w+\.]+\.\w+\b)/g;
let tmp = text.replace(patt,"<a href='mailto:$1'>$1</a>");
//<p>如有需要,請發(fā)郵件到 <a href='mailto:xxx@gmail.com'>xxx@gmail.com</a> </p>

上面的場景是將郵箱地址替換為可點(diǎn)擊的鏈接,在js中使用$符號來獲取回溯引用。

2.7 前后查找

前后查找(Lookaround)有時(shí)又叫零寬度匹配,它和定位符(^,$,\b,\B)有點(diǎn)像,都是匹配的是位置,與定位符不同的是,定位符的位置是預(yù)先定義好的,要么是字符串的開始或結(jié)尾,要么就是單詞的邊界與非邊界。而前后查找可以通過子表達(dá)式(定位條件)來定位位置。

與單詞邊界\b相似,在前后查找中,定位的位置(表達(dá)式所匹配的內(nèi)容)也分為前與后,及向前查找與向后查找

類型 語法格式 說明
向前查找 (?=exp) 返回表達(dá)式exp所匹配內(nèi)容之前的位置
向后查找 (?<=exp) 返回表達(dá)式exp所匹配內(nèi)容之后的位置

前后查找也可以組合起來使用,比如要取標(biāo)簽p中的內(nèi)容:

let text = "<p>如有需要,請發(fā)郵件到 <a href='mailto:xxx@gmail.com'>xxx@gmail.com</a> </p>";

let patt =/(?<=<p>)[\s\S]*?(?=<\/p>)/g
let tmp = text.match(patt);
//["如有需要,請發(fā)郵件到 <a href='mailto:xxx@gmail.com'>xxx@gmail.com</a> "]

上面組合使用了向前查找和向后查找,(?<=<p>)是向后查找,返回條件<p>后面的位置,(?=<\/p>)是向前查找,返回</p>結(jié)束標(biāo)簽前面的位置。[\s\S]*?中間采用懶惰模式匹配兩個(gè)位置(及p標(biāo)簽)之間的內(nèi)容。

let text = "1234567890";
let patt = /\B(?=(\d{3})+$)/g

let tmp = text.replace(patt,",");
//1,234,567,890

text = "1234567890.54";
patt = /\B(?=(\d{3})+\.)/g
tmp = text.replace(patt,",");
//"1,234,567,890.54"

上例是給數(shù)字按3位加逗號,使用了向前查找表達(dá)式,整個(gè)表達(dá)式開始使用了\B,表示位置只會出現(xiàn)在字符串的非邊界上,防止逗號加在整個(gè)字符串的外面。(?=(\d{3})+$)是向前查找,返回滿足(\d{3})+$內(nèi)容前面的位置,注意表達(dá)式最后的定位符是$,可以理解為結(jié)束位置是字符串的最后。當(dāng)有小數(shù)點(diǎn)的時(shí)候,結(jié)束位置應(yīng)該換為\.

前后查找是查找滿足條件的內(nèi)容的前或后的位置。還有一種是查找不滿足條件的內(nèi)容的前后位置,稱為負(fù)向前后查找,這種用的比較少。

類型 語法格式 說明
負(fù)向前查找 (?!exp) 返回不能匹配表達(dá)式exp內(nèi)容之前的位置
負(fù)向后查找 (?<!exp) 返回不能匹配表達(dá)式exp內(nèi)容之后的位置

2.8 正則表達(dá)式中的條件

在處理復(fù)雜的業(yè)務(wù)邏輯時(shí),可能需要在正則表達(dá)式中嵌入條件,及只有滿足特定條件時(shí),才執(zhí)行相應(yīng)的表達(dá)式。嵌入條件語法使用?,它有兩種情況:

  • 根據(jù)回溯引用來進(jìn)行條件處理
  • 根據(jù)前后查找來進(jìn)行條件處理
類型 語法格式 說明
回溯引用 (?(ref)true_exp|false_exp) ref是前面的表達(dá)式的引用編號,這里填寫數(shù)字即可,從1開始(與回溯引用的\n相同),true_exp表示ref匹配到內(nèi)容時(shí)執(zhí)行,false_exp表示ref匹配不到內(nèi)容時(shí)執(zhí)行,false_exp是可選內(nèi)容
前后查找 (?(?=exp)true_exp|false_exp) (?=exp)是前后查找的表達(dá)式,也可以換為(?>=exp),true_exp表示前后查找能匹配時(shí)執(zhí)行,false_exp表示前后查找不能匹配時(shí)執(zhí)行,false_exp是可選內(nèi)容

3 參考

[1] 正則表達(dá)式30分鐘入門教程

[2] JavaScript高級程序設(shè)計(jì)(第3版)

[3] 正則表達(dá)式必知必會(修訂版)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容