《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參數類似):

  1. 找到第一個 “you”,並存儲其位置
  2. 如果再次運行 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 youconsole.log(s.replace(/you/, 'zichi')); // zichi love me and I love youconsole.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

常用匹配模式(持續更新)

  1.  只能輸入漢字:/^[\u4e00-\u9fa5]{0,}$/

總結

  1. test:檢查指定字符串中有沒有某子串(或某匹配模式),返回true或者false;如有必要可以進行全局模式搜索。
  2. exec:檢查指定字符串中有沒有某子串(或者匹配模式),如有返回數組(數組信息豐富,可參考上文介紹),如沒有返回null;如有必要可以進行全局搜索找出所有子串(或者匹配模式)的信息,信息中含有匹配模式中子表達式所對應的字符串
  3. compile:修改正則表達式中的pattern
  4. search:檢查指定字符串中有沒有某子串(或者匹配模式),如有返回子串(或者匹配模式)在原串中的開始位置,如沒有返回-1。不能進行全局搜索。
  5. match:檢查指定字符串中有沒有某子串(或者匹配模式),非全局模式下返回信息和exec一致;如進行全局搜索,直接返回字符串數組。(如不需要關於每個匹配的更多信息,推薦用match而不是exec)
  6. replace:檢查指定字符串中有沒有某子串(或者匹配模式),並用另一個子串代替(該子串可以跟原字符串或者搜索到的子串有關);如啓動g,則全局替換,否則只替換第一個。replace方法可以引用子表達式所對應的值
  7. split:用特定模式分割字符串,返回一個字符串數組;與Array的join方法正好相反。
  8. 子表達式:用括號括起來的正則匹配表達式,用後向引用可以對其進行引用;也可以和exec或者replace搭配獲取其真實匹配值。
  9. 後向引用 :對子表達式所在分組進行引用。
  10. 零寬斷言:和\b ^ 以及$類似的某個位置概念。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章