我所認識的JavaScript正規表示式
如果說這是一篇關於正規表示式的小結,我更願意把它當做一個手冊。
RegExp 三大方法
本文的RegExp採用直接量語法表示:/pattern/attributes。attributes有三個選擇,i、m和g,m(多行匹配)不常用直接省略,所以一個pattern(匹配模式)可以表示如下:
var pattern = /hello/ig;
i(ignore)表示不區分大小寫(地搜尋匹配),比較簡單,以下例子中不加述說;g(global)表示全域性(搜尋匹配),即找到一個後繼續找下去,相對複雜,以下各種方法中會特別介紹。
既然是RegExp的三大方法,所以都是pattern.test/exec/complie的格式。
- test
主要功能:檢測指定字串是否含有某個子串(或者匹配模式),返回true或者false。
示例如下:
var s = 'you love me and I love you'; var pattern = /you/; var ans = pattern.test(s); console.log(ans); // true
如果attributes用了g,則可以繼續找下去,其中還會涉及lastIndex屬性(參照exec中搭配g的介紹)。
- exec
主要功能:提取指定字串中的符合要求的子串(或者匹配模式),返回一個陣列存放匹配結果;如果沒有,則返回null。(也可自己寫方法迴圈提取所有或者指定index的資料)
exec可以說是test的升級版本,因為它不僅可以檢測,而且檢測到了可以直接提取結果。
示例如下:
var s = 'you love me and I love you'; var pattern = /you/; var ans = pattern.exec(s); console.log(ans); // ["you", index: 0, input: "you love me and I love you"] console.log(ans.index); // 0 console.log(ans.input); // you love me and I love you
輸出的東西很有意思。此陣列的第 0 個元素是與正規表示式相匹配的文字,第 1 個元素是與 RegExpObject 的第 1 個子表示式相匹配的文字(如果有的話),第 2 個元素是與 RegExpObject 的第 2 個子表示式相匹配的文字(如果有的話),以此類推。
啥叫“與子表示式相匹配的文字”?看下面的例子:
var s = 'you love me and I love you'; var pattern = /y(o?)u/; var ans = pattern.exec(s); console.log(ans); // ["you", "o", index: 0, input: "you love me and I love you"] console.log(ans.length) // 2
所謂的子表示式就是pattern裡()內的東西(具體可以參考下文對子表示式的介紹)。再看上面例子的陣列長度,是2!!index和input只是陣列屬性(chrome中以上的輸出可能會讓人誤會)。
除了陣列元素和 length 屬性之外,exec() 方法還返回兩個屬性。index 屬性宣告的是匹配文字的第一個字元的位置。input 屬性則存放的是被檢索的字串 string。我們可以看得出,在呼叫非全域性的 RegExp 物件的 exec() 方法時,返回的陣列與呼叫方法 String.match() 返回的陣列是相同的。
如果使用 “g” 引數,exec() 的工作原理如下(還是以上的例子 ps:如果test使用g引數類似):
- 找到第一個 “you”,並儲存其位置
- 如果再次執行 exec(),則從儲存的位置(lastIndex)開始檢索,並找到下一個 “you”,並儲存其位置
當 RegExpObject 是一個全域性正規表示式時,exec() 的行為就稍微複雜一些。它會在 RegExpObject 的 lastIndex 屬性指定的字元處開始檢索字串 string。當 exec() 找到了與表示式相匹配的文字時,在匹配後,它將把 RegExpObject 的 lastIndex 屬性設定為匹配文字的最後一個字元的下一個位置。這就是說,我們可以通過反覆呼叫 exec() 方法來遍歷字串中的所有匹配文字。當 exec() 再也找不到匹配的文字時,它將返回 null,並把 lastIndex 屬性重置為 0。這裡引入lastIndex屬性,這貨只有跟g和test(或者g和exec)三者搭配時才有作用。它是pattern的一個屬性,一個整數,標示開始下一次匹配的字元位置。
例項如下:
var s = 'you love me and I love you'; var pattern = /you/g; var ans; do { ans = pattern.exec(s); console.log(ans); console.log(pattern.lastIndex); } while (ans !== null)
結果如下:
應該還容易理解,當第三次迴圈時,找不到“you”了,於是返回null,lastIndex值也變成0了。
如果在一個字串中完成了一次模式匹配之後要開始檢索新的字串(仍然使用舊的pattern),就必須手動地把 lastIndex 屬性重置為 0。
- compile
主要功能:改變當前匹配模式(pattern)
這貨是改變匹配模式時用的,用處不大,略過。詳見JavaScript compile() 方法
String 四大護法
和RegExp三大方法分庭抗禮的是String的四大護法,四大護法有些和RegExp三大方法類似,有的更勝一籌。
既然是String家族下的四大護法,所以肯定是string在前,即str.search/match/replace/split形式。
既然是String的方法,當然引數可以只用字串而不用pattern。
- search
主要功能:搜尋指定字串中是否含有某子串(或者匹配模式),如有,返回子串在原串中的初始位置,如沒有,返回-1。
是不是和test類似呢?test只能判斷有木有,search還能返回位置!當然test()如果有需要能繼續找下去,而search則會自動忽略g(如果有的話)。例項如下:
var s = 'you love me and I love you'; var pattern = /you/; var ans = s.search(pattern); console.log(ans); // 0
話說和String的indexOf方法有點相似,不同的是indexOf方法可以從指定位置開始查詢,但是不支援正則。
- match
主要功能:和exec類似,從指定字串中查詢子串或者匹配模式,找到返回陣列,沒找到返回null
match是exec的輕量版,當不使用全域性模式匹配時,match和exec返回結果一致;當使用全域性模式匹配時,match直接返回一個字串陣列,獲得的資訊遠沒有exec多,但是使用方式簡單。
例項如下:
var s = 'you love me and I love you'; console.log(s.match(/you/)); // ["you", index: 0, input: "you love me and I love you"] console.log(s.match(/you/g)); // ["you", "you"]
- replace
主要功能:用另一個子串替換指定字串中的某子串(或者匹配模式),返回替換後的新的字串 str.replace(‘搜尋模式’,'替換的內容’) 如果用的是pattern並且帶g,則全部替換;否則替換第一處。
例項如下:
var s = 'you love me and I love you'; console.log(s.replace('you', 'zichi')); // zichi love me and I love you console.log(s.replace(/you/, 'zichi')); // zichi love me and I love you console.log(s.replace(/you/g, 'zichi')); // zichi love me and I love zichi
如果需要替代的內容不是指定的字串,而是跟匹配模式或者原字串有關,那麼就要用到$了(記住這些和$符號有關的東東只和replace有關哦)。
怎麼用?看個例子就明白了。
var s = 'I love you'; var pattern = /love/; var ans = s.replace(pattern, '$`' + '$&' + "$'"); console.log(ans); // I I love you you
沒錯,’$`’ + ‘$&’ + “$’”其實就相當於原串了!
replace的第二個引數還能是函式,看具體例子前先看一段介紹:
注意:第一個引數是匹配到的子串,接下去是子表示式匹配的值,如果要用子表示式引數,則必須要有第一個引數(表示匹配到的串),也就是說,如果要用第n個引數代表的值,則左邊引數都必須寫出來。最後兩個引數跟exec後返回的陣列的兩個屬性差不多。
var s = 'I love you'; var pattern = /love/; var ans = s.replace(pattern, function(a) { // 只有一個引數,預設為匹配到的串(如還有引數,則按序表示子表示式和其他兩個引數) return a.toUpperCase(); }); console.log(ans); // I LOVE you
- split
主要功能:分割字串
字串分割成字串陣列的方法(另有陣列變成字串的join方法)。直接看以下例子:
var s = 'you love me and I love you'; var pattern = 'and'; var ans = s.split(pattern); console.log(ans); // ["you love me ", " I love you"]
如果你嫌得到的陣列會過於龐大,也可以自己定義陣列大小,加個引數即可:
var s = 'you love me and I love you'; var pattern = /and/; var ans = s.split(pattern, 1); console.log(ans); // ["you love me "]
RegExp 字元
- \s 任意空白字元 \S相反 空白字元可以是: 空格符 (space character) 製表符 (tab character) 回車符 (carriage return character) 換行符 (new line character) 垂直換行符 (vertical tab character) 換頁符 (form feed character)
- \b是正規表示式規定的一個特殊程式碼,代表著單詞的開頭或結尾,也就是單詞的分界處。雖然通常英文的單詞是由空格,標點符號或者換行來分隔的,但是\b並不匹配這些單詞分隔字元中的任何一個,它只匹配一個位置。(和^ $ 以及零寬斷言類似)
- \w 匹配字母或數字或下劃線 [a-z0-9A-Z_]完全等同於\w
貪婪匹配和懶惰匹配
什麼是貪婪匹配?貪婪匹配就是在正規表示式的匹配過程中,預設會使得匹配長度越大越好。
var s = 'hello world welcome to my world'; var pattern = /hello.*world/; var ans = pattern.exec(s); console.log(ans) // ["hello world welcome to my world", index: 0, input: "hello world welcome to my world"]
以上例子不會匹配最前面的Hello World,而是一直貪心的往後匹配。
那麼我需要最短的匹配怎麼辦?很簡單,加個‘?’即可,這就是傳說中的懶惰匹配,即匹配到了,就不往後找了。
var s = 'hello world welcome to my world'; var pattern = /hello.*?world/; var ans = pattern.exec(s); console.log(ans) // ["hello world", index: 0, input: "hello world welcome to my world"]
懶惰限定符(?)新增的場景如下:
子表示式
- 表示方式
用一個小括號指定:
var s = 'hello world'; var pattern = /(hello)/; var ans = pattern.exec(s); console.log(ans);
- 子表示式出現場景
在exec中陣列輸出子表示式所匹配的值:
var s = 'hello world'; var pattern = /(h(e)llo)/; var ans = pattern.exec(s); console.log(ans); // ["hello", "hello", "e", index: 0, input: "hello world"]
在replace中作為替換值引用:
var s = 'hello world'; var pattern = /(h\w*o)\s*(w\w*d)/; var ans = s.replace(pattern, '$2 $1') console.log(ans); // world hello
後向引用 & 零寬斷言
- 子表示式的序號問題
簡單地說:從左向右,以分組的左括號為標誌,第一個出現的分組的組號為1,第二個為2,以此類推。
複雜地說:分組0對應整個正規表示式實際上組號分配過程是要從左向右掃描兩遍的:第一遍只給未命名組分配,第二遍只給命名組分配--因此所有命名組的組號都大於未命名的組號。可以使用(?:exp)這樣的語法來剝奪一個分組對組號分配的參與權.
後向引用
如果我們要找連續兩個一樣的字元,比如要找兩個連續的c,可以這樣/c{2}/,如果要找兩個連續的單詞hello,可以這樣/(hello){2}/,但是要在一個字串中找連續兩個相同的任意單詞呢,比如一個字串hellohellochinaworldworld,我要找的是hello和world,怎麼找?
這時候就要用後向引用。看具體例子:
var s = 'hellohellochinaworldworld'; var pattern = /(\w+)\1/g; var a = s.match(pattern); console.log(a); // ["hellohello", "worldworld"]
這裡的\1就表示和匹配模式中的第一個子表示式(分組)一樣的內容,\2表示和第二個子表示式(如果有的話)一樣的內容,\3 \4 以此類推。(也可以自己命名,詳見參考文獻)
或許你覺得陣列裡兩個hello兩個world太多了,我只要一個就夠了,就又要用到子表示式了。因為match方法裡是不能引用子表示式的值的,我們回顧下哪些方法是可以的?沒錯,exec和replace是可以的!
exec方式:
var s = 'hellohellochinaworldworld'; var pattern = /(\w+)\1/g; var ans; do { ans = pattern.exec(s); console.log(ans); } while(ans !== null); // result // ["hellohello", "hello", index: 0, input: "hellohellochinaworldworld"] index.html:69 // ["worldworld", "world", index: 15, input: "hellohellochinaworldworld"] index.html:69 // null
如果輸出只要hello和world,console.log(ans[1])即可。
replace方式:
var s = 'hellohellochinaworldworld'; var pattern = /(\w+)\1/g; var ans = []; s.replace(pattern, function(a, b) { ans.push(b); }); console.log(ans); // ["hello", "world"]
如果要找連續n個相同的串,比如說要找出一個字串中出現最多的字元:
String.prototype.getMost = function() { var a = this.split(''); a.sort(); var s = a.join(''); var pattern = /(\w)\1*/g; var a = s.match(pattern); a.sort(function(a, b) { return a.length < b.length; }); var letter = a[0][0]; var num = a[0].length; return letter + ': ' + num; } var s = 'aaabbbcccaaabbbcccccc'; console.log(s.getMost()); // c: 9
如果需要引用某個子表示式(分組),請認準後向引用!
零寬斷言
別被名詞嚇壞了,其實解釋很簡單。
它們用於查詢在某些內容(但並不包括這些內容)之後的東西,也就是說它們像\b,^,$那樣用於指定一個位置,這個位置應該滿足一定的條件(即斷言)
- (?=exp)
零寬度正預測先行斷言,它斷言自身出現的位置的後面能匹配表示式exp。
// 獲取字串中以ing結尾的單詞的前半部分 var s = 'I love dancing but he likes singing'; var pattern = /\b\w+(?=ing\b)/g; var ans = s.match(pattern); console.log(ans); // ["danc", "sing"]
- (?!exp)
零寬度負預測先行斷言,斷言此位置的後面不能匹配表示式exp
// 獲取第五位不是i的單詞的前四位 var s = 'I love dancing but he likes singing'; var pattern = /\b\w{4}(?!i)/g; var ans = s.match(pattern); console.log(ans); // ["love", "like"]
javascript正則只支援前瞻,不支援後瞻((?<=exp)和(?<!exp))。
關於零寬斷言的具體應用可以參考綜合應用一節給字串加千分符。
其他
- 字元轉義
因為某些字元已經被正規表示式用掉了,比如. * ( ) / \ [],所以需要使用它們(作為字元)時,需要用\轉義
var s = 'http://www.cnblogs.com/zichi/'; var pattern = /http:\/\/www\.cnblogs\.com\/zichi\//; var ans = pattern.exec(s); console.log(ans); // ["http://www.cnblogs.com/zichi/", index: 0, input: "http://www.cnblogs.com/zichi/"]
- 分支條件
如果需要匹配abc裡的任意字母,可以用[abc],但是如果不是單個字母那麼簡單,就要用到分支條件。
分支條件很簡單,就是用|表示符合其中任意一種規則。
var s = "I don't like you but I love you"; var pattern = /I.*(like|love).*you/g; var ans = s.match(pattern); console.log(ans); // ["I don't like you but I love you"]
答案執行了貪婪匹配,如果需要懶惰匹配,則:
var s = "I don't like you but I love you"; var pattern = /I.*?(like|love).*?you/g; var ans = s.match(pattern); console.log(ans); // ["I don't like you", "I love you"]
綜合應用
- 去除字串首尾空格(replace)
String.prototype.trim = function() { return this.replace(/(^\s*)|(\s*$)/g, ""); }; var s = ' hello world '; var ans = s.trim(); console.log(ans.length); // 12
- 給字串加千分符(零寬斷言)
String.prototype.getAns = function() { var pattern = /(?=((?!\b)\d{3})+$)/g; return this.replace(pattern, ','); } var s = '123456789'; console.log(s.getAns()); // 123,456,789
- 找出字串中出現最多的字元(後向引用)
String.prototype.getMost = function() { var a = this.split(''); a.sort(); var s = a.join(''); var pattern = /(\w)\1*/g; var a = s.match(pattern); a.sort(function(a, b) { return a.length < b.length; }); var letter = a[0][0]; var num = a[0].length; return letter + ': ' + num; } var s = 'aaabbbcccaaabbbcccccc'; console.log(s.getMost()); // c: 9
常用匹配模式(持續更新)
- 只能輸入漢字:/^[\u4e00-\u9fa5]{0,}$/
總結
- test:檢查指定字串中有沒有某子串(或某匹配模式),返回true或者false;如有必要可以進行全域性模式搜尋。
- exec:檢查指定字串中有沒有某子串(或者匹配模式),如有返回陣列(陣列資訊豐富,可參考上文介紹),如沒有返回null;如有必要可以進行全域性搜尋找出所有子串(或者匹配模式)的資訊,資訊中含有匹配模式中子表示式所對應的字串。
- compile:修改正規表示式中的pattern
- search:檢查指定字串中有沒有某子串(或者匹配模式),如有返回子串(或者匹配模式)在原串中的開始位置,如沒有返回-1。不能進行全域性搜尋。
- match:檢查指定字串中有沒有某子串(或者匹配模式),非全域性模式下返回資訊和exec一致;如進行全域性搜尋,直接返回字串陣列。(如不需要關於每個匹配的更多資訊,推薦用match而不是exec)
- replace:檢查指定字串中有沒有某子串(或者匹配模式),並用另一個子串代替(該子串可以跟原字串或者搜尋到的子串有關);如啟動g,則全域性替換,否則只替換第一個。replace方法可以引用子表示式所對應的值。
- split:用特定模式分割字串,返回一個字串陣列;與Array的join方法正好相反。
- 子表示式:用括號括起來的正則匹配表示式,用後向引用可以對其進行引用;也可以和exec或者replace搭配獲取其真實匹配值。
- 後向引用 :對子表示式所在分組進行引用。
- 零寬斷言:和\b ^ 以及$類似的某個位置概念。
相關文章
- 系統認識JavaScript正規表示式JavaScript
- 我所理解的正規表示式
- javascript正規表示式 | 知識梳理JavaScript
- JavaScript的正規表示式JavaScript
- 【JavaScript】正規表示式JavaScript
- javascript正規表示式JavaScript
- javascript–正規表示式JavaScript
- JavaScript 正規表示式JavaScript
- 正規表示式學習筆記(1)-認識正則筆記
- 我理解的正規表示式
- JavaScript正規表示式(2)JavaScript
- 正規表示式知識(+)
- JavaScript正規表示式 手冊JavaScript
- 淺談JavaScript正規表示式JavaScript
- JavaScript匹配中文正規表示式JavaScript
- javascript正規表示式總結JavaScript
- 初學JavaScript正規表示式JavaScript
- 16、JavaScript-正規表示式JavaScript
- javascript正規表示式小技巧JavaScript
- 正規表示式知識點
- [記錄]javascript常用的正規表示式JavaScript
- javascript常用的驗證正規表示式JavaScript
- 匹配javascript註釋的正規表示式JavaScript
- JavaScript中的正規表示式(2) (轉)JavaScript
- JavaScript中的正規表示式(1) (轉)JavaScript
- JavaScript正規表示式進階指南JavaScript
- JavaScript正規表示式方法總結JavaScript
- JavaScript匹配註釋正規表示式JavaScript
- JavaScript學習1:正規表示式JavaScript
- Javascript正規表示式詳解(一)JavaScript
- Javascript校驗與正規表示式JavaScript
- 正規表示式基礎知識
- 【正規表示式】常用的正規表示式(數字,漢字,字串,金額等的正規表示式)字串
- JavaScript基礎-06-正規表示式JavaScript
- JavaScript RegExp(正規表示式) 物件JavaScript物件
- 《JavaScript 闖關記》之正規表示式JavaScript
- JavaScript正規表示式迷你書-筆記JavaScript筆記
- 簡單學習 JavaScript 正規表示式JavaScript