正則表示式-基礎知識Review
正則表示式(Regular Expression)是電腦科學的一個概念。 正則表示式使用單個字元竄來描述、匹配一系列符合某個句法規則的字元竄。 在很多文字編輯器裡, 正則表示式通常用來被檢索替換哪些符合某個模式的文字。
建立
JavaScript通過內建物件 RegExp支援正則表示式, 有兩種方式建立正則表示式物件, 如果我們想匹配字元竄中<%xxx%>兩個百分號分割的字元竄可以這麼寫。
- 建構函式
var regExp = new RegExp('<%[^%>]+%', 'g');
- 字面量
var regExp = /<%[^%>]%>/g;
最後的g代表全域性, 還有幾個修飾符
RegExp例項物件有五個屬性
- g: global, 全文搜尋,不新增的話搜尋到第一個結果停止搜尋。
- i: ignore case, 忽略大小寫,預設大小寫敏感。
- m: multiple lines, 多行搜尋。
- lastIndex: 是當前表示式模式首次匹配內容中最後一個字元的下一個位置,每次正則表示式匹配成功匹配時, lastIndex屬性值都會隨之改變。
- sourse:正則表示式的文字字元竄。
除了正則表示式編譯為內部格式從而使執行更快的compile()方法, 物件還有兩個我們常用的方法。
元字元
正則表示式讓人望而卻步的一個重要原因就是轉譯字元太多, 組合非常多, 但是正則表示式的元字元(在正則表示式中具有特殊意義的專用字元,可以用來規定其前導字元)並不多
() [] {} \ ^ $ | ? * + .
並不是每個元字元都有特定的意義,在不同的組合中元字元有不同的意義, 分類看一下
- t 水平製表符 tab
- r 回車符 carriage return
- n 換行符 newline
- f page feed 換頁符
- cX 與X對應的控制字元(Ctrl + X )
- v 垂直製表符
- 0 空字元
字元類
一般情況下正則表示式一個字元(轉譯字元算一個) 對應字元竄一個字元,表示式 abt 的含義
ab水平製表符
但是我們可以使用元字元[]來構建一個簡單的類, 所謂類是指,符合某些特徵的物件, 是一個泛指,而不是特指某個字元了, 我們可以使用表示式[abc], 把字元a或b或c歸為一類,表示式可以匹配這類的字元。
字元類取反
元字元[]組合可以建立一個類,我們還可以使用元字元^建立反向類/負向類,反向類的意思是不屬於XXX類的內容, 表示式1表示不是字元a或b或c的內容。
範圍類
按照上面的說明如果希望匹配單個數字那麼表示式是這樣的
[0123456789]
如果是字母那麼。。。 好麻煩, 正則表示式還提供了範圍類,我們可以使用x-y來連線兩個字元表示從x到y的任意字元, 這是個閉區間, 也就是說包含x和y本身, 這樣匹配小寫字母就很簡單了。
[a-z]
預定義類
剛才使用正則我們建立了幾個類,來表示數字,字母等,但這樣寫很麻煩,正則表示式為我們了幾個常用的預定義類來匹配常見的字元。
字元 | 等價類 | 含義 |
---|---|---|
. | [^rt] | 除了回車符和換行符之外的所有字元 |
d | [0-9] | 數字字元 |
D | [^0-9] | 數字字元 |
s | [\t\n\x0B\f\r] | 空白符 |
S | [^t\n\x0B\f\r] | 非空白符 |
w | [a-zA-Z_0-9] | 字母,數字,下劃線 |
W | [^a-zA-Z_0-9] | 非字母,數字,下劃線 |
有了這些預定義類, 寫一些正則就很方便了, 比如我們希望匹配一個 ab + 數字 + 任意字元的字元竄, 就可以寫了abd.
可以把正則表示式,想象成一種模式,字元竄匹配正則表示式定義的模式的結果
string.match(regExpPattern)
邊界
正則表示式還提供了幾個常用的邊界匹配字元。
字元 | 含義 |
---|---|
^ | 以xxx開頭, ^ 開頭的匹配參照物件是整個字元竄 |
$ | 以xxx結尾, $ 結尾的匹配參照物件也是整個字元竄 |
b | 單詞邊界, '-'也是單詞邊界! |
B | 非單詞邊界 |
^ 開頭的匹配參照物件是整個字元竄,
$ 結尾的匹配參照物件也是整個字元竄,
限制的是整個字元竄。
var regExpPattern = /^h\dm/g;
'h2m h3m h2m'.match(regExpPattern); // h2m, 記住這是以整個字元竄去匹配的
//同理
var regExpExpPattern = /h\dm$/g;
'h2m h3m h3m'.match(regExpPattern); // h3m
//使用場景 手機號匹配
'13423454234'.match(/^1\d{10}$/g)
量詞
之前,介紹的方法都是一一匹配,如果希望匹配一個連續20次數字的字元竄難道我們需要寫成這樣?
\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d..
為此正則表示式引入了一些量詞
字元 | 含義 |
---|---|
? | 出現零次或一次(最多出現一次) |
+ | 出現一次或多次(至少出現一次) |
* | 出現零次或多次(任意次) |
{n} | 出現n次 |
{n,m} | 出現n到m次 |
{n,} | 至少出現n次 |
{,m} | 最多出現m次 |
var regExp = /w+\bBryon/
regExp.test('hi Bryon'); // true
regExp.test('Welcome Byron'); // true
regExp.test('Byron'); //false
//匹配帶有3到1個小數的數字
var reg = /\d+\.d{1,3}
貪婪模式與非貪婪模式
看了量詞的介紹,也許愛思考的同學會想到關於匹配原則的一些問題, 比如{3,5} 這個量詞, 要是在句子中出現了十次, 那麼他是每次匹配三個還是五個,反正3,4,5都滿足3~5的條件。
量詞在預設下是儘可能多的匹配的,也就是大家常說的貪婪模式。
// ['12345','6789'];
'12345679'.match(/\d{3,5}/g);
既然有貪婪模式,那麼肯定會有非貪婪模式,讓正則表示式儘可能少的匹配,u額就是說一旦成功匹配不再繼續嘗試,做法很簡單, 在量詞後加上?即可
//['123','456','789']
'123456789'.match(/\d{3,5}?/g);
分組
有時候希望使用量詞的時候匹配多個字元,而不是像上面例子只是匹配一個,比如希望匹配Byron出現20次的字元竄,寫成Byron{20}的話匹配的是Byro+n出現20次。
//寫成Byron{20}的話匹配的是Byro+n出現20次。
/Byron{20}/g
怎麼把Byron作為一個整體呢? 使用()就可以達到此目的,在正則中成為分組。
//分組在regExp.exec(stringObj)中會單獨顯示在返回結果中。
(Byron){20}
或 |
如果希望匹配Byron或Casper出現20次該怎麼辦呢? 可以使用字元| 達到或的功效
(Byron|Casper){20}
使用分組的正則表示式會把匹配項也放到分組中,預設是按數字編號分發的,可以根據編號獲得捕獲的分組內容,這在一些希望具體操作第幾個匹配項的函式中很有用。
// Group 1 : Byron , Group 2: ok
(Byron).(ok)
分組巢狀
如果有分組巢狀的情況,外面的組的編號靠前
// group 1: (^|%>)[^\t]*, Group 2: (^|%>)
((^|%>)[^\t]*)
忽略分組
有時候我們不希望捕獲某些分組,只需要在分組內加上?: 就可以了, 這並不意味著該分組內容不屬於正則表示式,只是不會給這個分組加編號而已。
(?:Bryon).(ok)
前瞻
表示式 | 含義 |
---|---|
exp1(?=exp2) | 匹配後面是exp2的exp1 |
exp1(?!exp2) | 匹配後面不是exp2的exp1 |
var regExp = /good(?=Byron)/;
regExp.exec('goodByron123'); // ['good']
regExp.exec('goodCapser123'); // null
通過上面例子可以看出exp1(?=exp2)表示式, 但只有其後面內容是exp2的時候才會匹配,也就是兩個條件,exp1(?!exp2)比較類似。
正則表示式相關的方法
- RegExp.prototype.test(str)
該方法用於測試字元竄引數中是否匹配正則表示式模式,如果存在則返回true,否則返回false。
var reg = /\d+\.\d{1,2}$/g;
reg.test('123.45'); // true
reg.test('0.2'); // true
reg.test('a.34'); //false
reg.test('34.5678'); //false
//判斷手機號是否正確
function isPhoneNum(phoneNum) {
var phoneNumberTester = /^1\d{10}$/;
return phoneNumberTester.test(phoneNum);
}
- RegExp.prototype.exec(str)
該方法用於正則表示式模式在字元竄中執行查詢,如果exec()找到匹配的文字,則返回一個結果,否則返回null
除了陣列元素和length屬性之外, exec()方法返回物件還包括兩個屬性。
- index屬性宣告的是匹配文字的第一個字元的位置。
- input屬性則存放的是被檢索的字元竄string。
非全域性呼叫
呼叫非全域性的RegExp物件的exec()時,返回陣列的第一個元素是與正則表示式相匹配的文字,第二個元素是與RegExpObject的第一個子表示式相匹配的文字(如果有的話), 第三個元素時與RegExp物件的第二個子表示式相匹配的文字(如果有的話),以此類推。
全域性呼叫
呼叫全域性的RegExp物件的exec()時,它會在RegExp例項的lastIndex屬性指定的字元處開始檢索字元竄string
- 當exec()找到了與表示式相匹配的文字時, 在匹配後, 它將把RegExp例項的lastIndex屬性設定為匹配文字的最後一個字元的下一個位置。可以通過反覆呼叫exec()方法來遍歷字元竄中的所有哦匹配文字。
- 當exec()再也找不到匹配的文字時,它將返回null,並把lastIndex屬重置為0。
var reg = /\d/g;
var r = reg.exec('a1b2c3');
console.log(r);
console.log(reg.lastIndex); // 2
r = reg.exec('a1b2c3');
console.log(reg.lastIndex); // 4
var reg = /\d/g;
while (r = reg.exec('a1b2c3')) {
console.log(r.index + ':' + r[0])
}
//1:1 , 3:2, 5:3
- String.prototype.search(reg)
search()方法用於檢索字元竄中指定的子字串,或檢索與正則表示式相匹配的子字元竄。
search()方法不執行全域性匹配,它將忽略標誌g, 它同時忽略正則表示式物件的lastIndex屬性,並且總是從字元竄的開始進行檢索,這意味著它總是返回字元竄的第一個匹配的位置。
- String.prototype.match(reg)
match()方法將檢索字元竄,以找到一個或多個與regexp匹配的文字。但regexp是否具有標誌g對結果影響很大。
非全域性呼叫
如果regexp沒有標誌g,那麼match()方法就只能在字元竄中執行一次匹配。如果沒有找到任何匹配的文字,match()將返回null。否則它將返回一個數組,其中存放了與它找到的匹配文字有關的資訊。
該陣列的第一個元素存放的是匹配文字,而其餘的元素存放的是與正則表示式的子表示式匹配的文字。除了這些常規的陣列元素之外,返回的陣列還含有兩個物件屬性。
- index屬性宣告的是匹配文字的起始字元在字元竄中的位置。
- input屬性宣告的是對stringObject的引用。
- String.prototype.replace(reg, replaceStr|| function callback())
關於string物件的replace方法, 我們最常用的是傳入兩個字元竄的做法,但這種做法有個缺陷,只能replace一次。
'abcabcabc'.replacce('bc', 'X') //aXabcabc
replace方法的第一個引數還可以傳入RegExp物件,傳入正則表示式可以使replace方法更加強大靈活
'abcabcabc'.replace(/bc/g, 'X'); //aXaXaX
'abcabcabc'.replace(bc/gi, 'X'); // aXaXaX
如果replace方法的第一個引數傳入的是帶分組的正則表示式,我們在第二個引數中可以使用$1...$9來獲取相應分組內容, 比如希望把字元竄1<%2%>34>%567%>89的<%x%>換為$#x#$, 我們可以這樣
'1<%2%>34>%567%>89的<%x%>'.replace(/<%(d+)%>)/g, '@#$1#@');// [email protected]#2#@[email protected]#567#@89
當然還有很多方式可以達到這一目的,這裡只是演示一下利用分組內容,我們在第二個引數中使用@#$1#@, 其中$1表示被捕獲的分組內容弄, 在一些js模板函式中可以經常見到這種方式替換字元竄。
可以通過修改replace方法的第二個引數,使replace更加強大,在前面的介紹中,只能把所有匹配替換為固定內容,但如果我希望把一個字元竄中所有數字,都用小括號包起來該怎麼弄?
'2398sdadads1smdsa3mmm23mmmbb'.replace(/\d+/g, function(matchedStr, groupContent$, index, strObj ) {
return '(' + matchedStr + ')';
})
把replace方法的第二個引數傳入一個function,這個function會在每次匹配替換的時候呼叫,算是個每次替換的回撥函式,我們使用了回撥函式的第一個引數,也就是匹配內容,其實回撥函式一共有四個引數。
- 第一個引數很簡單,是匹配字元竄。
- 第二個引數是正則表示式分組內容,沒有分組則沒有該引數。
- 第三個引數是匹配項在字元竄中的index。
- 第四個引數則是原字元竄。
例子
'2398rsjdhahd2131kksdajdj23'.replace(/\d+/g, function(matchedSubStr,index, strObj) {
console.log(matchedSubStr + '\t' + index + '\t' + strObj);
return '(' + matchedSubStr +')';
})
//2398 0 2398rsjdhahd2131kksdajdj23
//2131 12 2398rsjdhahd2131kksdajdj23
//23 24 2398rsjdhahd2131kksdajdj23
這是沒有分組的情況,打印出來的分別是匹配內容、匹配項index和原字元竄,看個有分組的。
'<%1%><%2%><%3%>'.replace(/<%([^%>]+)%>/g, function(matchedStr, group, index, thisStrObj) {
console.log(matchedStr + '\t' + group + '\t' + index + '\t' + thisStrObj);
return group;
})
//<%1%> 1 0 <%1%><%2%><%3%>
//<%2%> 2 5 <%1%><%2%><%3%>
//<%3%> 3 10 <%1%><%2%><%3%>
//"123"
- String.prototype.split(reg)
使用split方法把字元竄分割為字元陣列
'a,b,c,d'.split(',');
// ['a','b','c','d']
和replace方法類似,在一些複雜的分割情況下我們可以使用正則表示式解決
'a1b2c3d'.split(/\d/); // ['a','b','c','d']
- abc ↩