首頁 > web前端 > js教程 > 主體

大家都認識的JavaScript正規表示式詳解

黄舟
發布: 2017-03-14 15:06:35
原創
1201 人瀏覽過

如果說這是一篇關於正規表達式的小結,我更願意把它當作一個手冊。

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 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 + &#39;: &#39; + num;
}

var s = &#39;aaabbbcccaaabbbcccccc&#39;;
console.log(s.getMost()); // c: 9
登入後複製

如果需要引用某个子表达式(分组),请认准后向引用!

零宽断言

别被名词吓坏了,其实解释很简单。

它们用于查找在某些内容(但并不包括这些内容)之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言)

  • (?=exp)

零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。

// 获取字符串中以ing结尾的单词的前半部分
var s = &#39;I love dancing but he likes singing&#39;;
var pattern = /\b\w+(?=ing\b)/g;
var ans = s.match(pattern);
console.log(ans); // ["danc", "sing"]
登入後複製
  • (?!exp)

零宽度负预测先行断言,断言此位置的后面不能匹配表达式exp

// 获取第五位不是i的单词的前四位
var s = &#39;I love dancing but he likes singing&#39;;
var pattern = /\b\w{4}(?!i)/g;
var ans = s.match(pattern);
console.log(ans); // ["love", "like"]
登入後複製

javascript正则只支持前瞻,不支持后瞻((?<=exp)和(?

关于零宽断言的具体应用可以参考综合应用一节给字符串加千分符。

其他

  • 字符转义

因为某些字符已经被正则表达式用掉了,比如. * ( ) / \ [],所以需要使用它们(作为字符)时,需要用\转义

var s = &#39;http://www.cnblogs.com/zichi/&#39;;
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&#39;t like you but I love you";
var pattern = /I.*(like|love).*you/g;
var ans = s.match(pattern);
console.log(ans); // ["I don&#39;t like you but I love you"]
登入後複製

答案执行了贪婪匹配,如果需要懒惰匹配,则:

var s = "I don&#39;t like you but I love you";
var pattern = /I.*?(like|love).*?you/g;
var ans = s.match(pattern);
console.log(ans); // ["I don&#39;t like you", "I love you"]
登入後複製

综合应用

  • 去除字符串首尾空格(replace)

String.prototype.trim = function() {
  return this.replace(/(^\s*)|(\s*$)/g, "");
};
var s = &#39;    hello  world     &#39;;
var ans = s.trim();
console.log(ans.length);    // 12
登入後複製
  • 给字符串加千分符(零宽断言)

String.prototype.getAns = function() {
  var pattern = /(?=((?!\b)\d{3})+$)/g;
  return this.replace(pattern, &#39;,&#39;);
}

var s = &#39;123456789&#39;;
console.log(s.getAns());  // 123,456,789
登入後複製
  • 找出字符串中出现最多的字符(后向引用)

String.prototype.getMost = function() {
  var a = this.split(&#39;&#39;);
  a.sort();
  var s = a.join(&#39;&#39;);
  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 + &#39;: &#39; + num;
}

var s = &#39;aaabbbcccaaabbbcccccc&#39;;
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 ^ 以及$類似的某個位置概念。

#

以上是大家都認識的JavaScript正規表示式詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!