你真的懂JavaScript的正則嗎?

hinesboy發表於2017-05-03

本文內容主要出處為《JavaScript權威指南》(第六版),筆者只是在搬磚的同時整理思路,有誤望及時指出,感謝!

定義正規表示式

概述

對於正規表示式的概念我們就不多費口舌了...
在JavaScript中使用正規表示式進行模式匹配離不開RegExp物件,建立正則物件有兩種方式
1.正規表示式直接量(包含在一對/之間的字元)

var reg = /java/;
reg.test('java'); // true複製程式碼

2.new RegExp()

var reg = new RegExp('java');
reg.test('java'); // true複製程式碼

JavaScript也賦予了String物件進行模式匹配的方法,但是使用這些方法的時候同樣離不開RegExp
例如:

var str = 'java';
str.match(/java/); // /java/為正則直接量複製程式碼

StringRegExp 如何進行正則匹配,有哪些方法,會在下面兩節講解,這一節(定義正規表示式),主要講述如何定義正規表示式(本節會用到RegExptestexec 方法,如若不瞭解,可以與最後一節結合來理解)

正則——直接字元量

直接字元量表示字元在正規表示式中的表達形式。特殊字元需\轉義

字母、數字 -> 自身
\o -> NUL字元(\u0000)
\t -> 製表符(\u0009)
\n -> 換行符(\u000A)
\v -> 垂直製表符(\u000B)
\f -> 換頁符(\u000C)
\r -> 回車符(\u000D)
...複製程式碼

正則——字元類

將字元量放入 [] 中就成了字元類,字元類匹配它包含的任意一個字元

[abc] // 表示a 或者b 或者c
[^abc] // ^在這裡表示取反,除了a、b、c之外的所有字元
[a-z] //-表示連線,從a到z的任意字元複製程式碼

由上面我們可以知道,[0-9]表示任意數字,像這種常用的字元類,JavaScript給他們制訂了自己的特殊轉義字元。

. // 除換行符與其他Unicode行終止符以外的任意字元
\w // 等價於[a-zA-Z0-9_],大小寫字母、數字、下劃線63字元中任意一個
\W // 等價於[^a-zA-Z0-9_]
\s // 任何Unicode空白符
\S // 任何Unicode非空白符
\d // 等價於[0-9]
\D // 等價於[^0-9]
[\b] // \b放入[]中標識退格直接量複製程式碼

正則——重複

描述相同字元,多次出現

{n, m} // 最少重複n次,最多重複m次
{n, } // 至少重複n次
{n} //重複n次
? // 等價於 {0, 1}
+ // 等價於 {1,}
* // 等價於 {0,}複製程式碼

例如:

var reg = new RegExp('a{2,}');
var str = 'aaa';
var str2 = 'a';
reg.test(str); // true
reg.test(str2); // false複製程式碼

在這裡會多出一個概念:非貪婪重複
在上述例子中,reg.test(str) === true
我們用reg.exec(str) 獲取匹配結果會發現 結果為'aaa'
如果我們選擇使用非貪婪重複

var reg = new RegExp('a{2,}?');
var str = 'aaa';
reg.exec(str);複製程式碼

這時候結果為 'aa',實現非貪婪重複很簡單,在重複後面新增?即可,這時候,正規表示式會盡可能少的去匹配重複。

{n, m} -> {n, m}?
{n, } -> {n,}?
{n} -> {n}?
? -> ??
+ -> +?
* -> *?複製程式碼

選擇

| 可以分割用於選擇的字元,優先順序從左向右

ab|cd|ef // 表示 ab 或者 cd 或者 ef複製程式碼

子模式

() 在包裹子表示式同時將其定義為子模式
我們可以通過 \index 這種寫法在同一個正規表示式中呼叫子模式, index表示子模式的索引,從1開始。

var reg1 = /(java)script and \1/;
var reg2 = /javascript and java/;
// reg1 與 reg2 是兩個基本等價的正則直接量
var str = 'javascript and java';
reg1.test(str); // true
reg2.test(str); // true複製程式碼

子模式還有助於我們抽取子表示式匹配結果
把上述例子改為用exec方法並且列印

var reg1 = /(java)script and \1/;
var reg2 = /javascript and java/;
var str = 'javascript and java';
console.log(reg1.exec(str));
console.log(reg2.exec(str));複製程式碼

輸出結果為:

你真的懂JavaScript的正則嗎?

可以看到,當存在子模式時候,結果集會新增其子模式匹配結果,從中我們也可以看出,為什麼子模式index的索引會從1開始,因為0是完整的正則匹配結果。

當然,JavaScript允許我們在使用子表示式的時候不生成子模式
使用(?: )來包裹子表示式

var reg = /(?:java)script and java/;複製程式碼

此時我們無法通過\1找到其子模式,也無法獲取其子模式的匹配結果。

指定匹配位置

^ // 字串開始位置(在字元類中表示取反)
$ // 字串的結束位置
\b // 單詞邊界,也就是\w與\W的邊界
(?=p) // 要求字串與p匹配,但是結果集並不包含匹配p的字元
(?!p) // 要求字串不與p匹配複製程式碼

^ 與 $

/^javascript/ // 字串以javascript開始
/javascript$/ // 字串以javascript結束複製程式碼

\b

var reg = /\bjava\b/;
var str1 = 'java';
var str2 ='javascript';
var str3 = 'java c++';
var str4 = 'html java c++';
reg.test(str1); // true
reg.test(str2); // false
reg.test(str3); // true
reg.test(str4); // true
在這裡 \b 匹配非\w字元,包括字串起始與結束。
\B與之相反,匹配非單詞邊界處複製程式碼

(?=)

var reg = /java(?=script)/;
var str = 'java';
var str1 = 'javascript';
reg.exec(str); // 匹配失敗 , 因為不包含script
reg.exec(str1); // 此時匹配成公,但是匹配結果並不包含script複製程式碼

輸出結果為:

你真的懂JavaScript的正則嗎?

(?!)

var reg = /java(?!script)/;
var str = 'javaee';
var str1 = 'javascript';
reg.exec(str); // 匹配成功,匹配結果為java
reg.exec(str1); // 匹配失敗,因為包含script複製程式碼

修飾符

i // 不區分大小寫
m // 匹配多行(使用^ $指定起止時候能通融\n換行)
g // 匹配成功第一處,並不會繼續停止,會繼續尋找所有匹配複製程式碼

通過直接量建立正則物件新增修飾符: /java/gim (使用多個修飾詞直接並列)
通過建構函式建立正則物件新增修飾符: new RegExp(reg , 'gim');

通過String的模式匹配

String物件提供四種方法用於正則匹配。

search()

str.search(reg)
匹配成功返回起始位置,失敗返回-1,在search方法中修飾詞g不生效

var str = 'hello java';
str.search(/java/); // 6
str.search(/^java/); // -1複製程式碼

match()

str.match(reg)
匹配失敗返回null,匹配成功返回的是一個由匹配結果組成的陣列。如果該正則 表示式設定了修飾符g,則該方法返回的陣列包含字串中的所有匹配結果。

var str = 'hello java and javascript';
str.match(/java/); 
str.match(/java/g);複製程式碼

輸出結果為:

你真的懂JavaScript的正則嗎?

replace()

兩種呼叫方式,第二個引數不同

  • str.replace(reg , replaceStr)

    var str = 'javaee javaee';
    // str1 = 'javascript javaee'
    var str1 = str.replace(/e{2}/ , 'script'); 
    // str2 = 'javascript javascript' 修飾符g表示全域性替換
    var str2 = str.replace(/e{2}/g , 'script'); 
    // 補充知識點:str 依然是 javaee ,至於原因,簡單說就是字串直接量不可改變,字串所有的有關於值的改變的方法都是return新值。複製程式碼

    第二個引數replaceStr,用於替換的字串有一些特殊的寫法

    var reg = /"([^"]*)"/g; // 匹配 "" 間的內容,且內容不包含"
    var str = '"java","c++" and "html"';
    var str1 = str.replace(reg , '“$1”');
    console.log(str1) // “java”,“c++” and “html”複製程式碼

    此時,$1表示子模式([^"]*)匹配的結果集, 與我們上一節定義正規表示式中的呼叫自己的子模式\1相仿。
    同樣,在此處$還有其他幾種用法

    $index // 就是上述例子中的$1
    $& // 表示原字串  -> "java","c++" and "html"
    $` // 匹配字串的左側值,在上述例子中匹配成功三次,分別為: 空值 、 "java", 、"java","c++" and 
    $' // 匹配字串的右側值,在上述例子中匹配成功三次,分別為: ,"c++" and "html" 、  and "html" 、空值
    $$ // 字元常量$複製程式碼
  • str.replace(reg , function)

    var reg = /"([^"]*)"/g; // 匹配 "" 間的內容,且內容不包含"
    var str = '"java","c++" and "html"';
    var str1 = str.replace(reg , function (...arr) {
      console.log(arr)
    });複製程式碼

    輸出結果為:

    你真的懂JavaScript的正則嗎?

    匹配成功了三次,所以輸出了三次arr陣列,這個陣列裡面的元素分別為:
    0: 匹配結果
    ...: 若存在子模式,這裡為子模式匹配結果
    last - 1: 索引位置
    last: 原字串

    我們可以根據自己想要的結果,動態替換內容,通過return將結果替換到新的字串中。

     var reg = /"([^"]*)"/g; // 匹配 "" 間的內容,且內容不包含"
     var str = '"java","c++" and "html"';
     var str1 = str.replace(reg , function (...arr) {
      return `“${arr[1]}”`
     });
     console.log(str1) // “java”,“c++” and “html”複製程式碼

    split()

    • str.split(分隔符)
      根據分隔符分割返回新陣列
    • str.split(reg)
      根據正則分割返回新陣列
      var str = 'a,b-c,e,f-g';
      var arr = str.split(/[,-]/);
      console.log(arr) // ['a','b','c','d','e','f','g']複製程式碼

通過RegExp的模式匹配

建構函式

var reg = new RegExp('\\w'); // 通過正規表示式字串
var reg1 = new RegExp(/\w/); // 通過正規表示式直接量
var reg2 = new RegExp(reg , 'gim'); // 第二個引數為修飾符複製程式碼

屬性

  • source: String 正規表示式文字值
  • global: Boolean 是否攜帶全域性修飾符g
  • ignoreCase:Boolean 是否攜帶忽略大小寫修飾符i
  • multiline:Boolean 是否攜帶匹配多行修飾符m
  • lastIndex: Number 如果global === true , 那麼這個引數記錄每次匹配後的索引位置,下面的testexec方法會用到

方法

  • reg.exec(str)
    如果匹配失敗,返回null,匹配成功,返回匹配結果,每次執行僅返回一個匹配結果,若global === true,每次匹配成功後,會把lastIndex 屬性設定為緊挨著 匹配子串的字元位置。再次呼叫此方法,會從當前位置開始匹配(當我們使用同一個RegExp匹配新的字串的時候,最好把lastIndex屬性設定為0)
    var reg = new RegExp('java' , 'g');
    var str = 'javascript java javaee';
    var result = reg.exec(str);
    while (result) {
      console.log(result)
      console.log(`lastIndex = ${reg.lastIndex}`)
      result = reg.exec(str);
    }複製程式碼
    輸出結果為:
    你真的懂JavaScript的正則嗎?

當我們通過正規表示式直接量來呼叫進行正則運算的時候,並不會出現這種情況,那是因為在ES5中,每次通過直接量進行正則運算,JavaScript都會生成新的RegExp物件。

  • reg.test(str)
    test方法與exec基本等價,不同的是他們的返回值,test比較簡單粗暴,當exec返回null時候,它返回false,其他情況返回true

相關文章