JavaScript正規表示式學習筆記(二) - 打怪升級

景科同學發表於2018-04-09

JavaScript正規表示式學習筆記(二) - 打怪升級.png

本文接上篇,基礎部分相對薄弱的同學請移步《JavaScript正規表示式學習筆記(一) - 理論基礎》。上文介紹了8種JavaScript正規表示式的屬性,本文還會追加介紹幾種JavaScript正規表示式的屬性(注意是非標準屬性,但很好用)。

一. 上文回顧

本文會用到上篇文章部分內容,所以簡單回顧下。

1.1 JavaScript正規表示式標誌符

  1. g: 全域性匹配,即找到所有匹配的。對應屬性RegExp#global

  2. i: 忽略字母大小寫。對應屬性RegExp#ingoreCase

  3. m: 多行匹配,隻影響^和$,二者變成行的概念,即行開頭和行結尾。對應屬性RegExp#multiline

  4. u: ES6新增。含義為“Unicode 模式”,用來正確處理大於\uFFFF的 Unicode 字元。也就是說,會正確處理四個位元組的 UTF-16 編碼。對應屬性RegExp#unicode

  5. y: ES6新增。y修飾符的作用與g修飾符類似,也是全域性匹配,後一次匹配都從上一次匹配成功的下一個位置開始。不同之處在於,g修飾符只要剩餘位置中存在匹配就可,而y修飾符確保匹配必須從剩餘的第一個位置開始,這也就是“粘連”的涵義。對應屬性RegExp#sticky

1.2 適用於Javascript正規表示式的方法

上篇文章《JavaScript正規表示式學習筆記(一) - 理論基礎》介紹了適用於JavaScript正規表示式模式匹配的相關API共有6種,RexExp提供2個,String提供4個,如下:

1. RegExp#test    // 適用於:驗證、提取
2. RegExp#exec    // 適用於:驗證、提取
3. String#search  // 適用於:驗證、提取
4. String#match   // 適用於:驗證、提取
5. String#split   // 適用於:切分
6. String#replace // 適用於:提取、替換
複製程式碼

二. JavaScript正規表示式的四種操作

正規表示式是用於匹配字串中字元組合的模式, 其核心內容便是模式匹配。也就是說,不論進行那種操作,首先要有模式匹配,有了模式匹配之後,才能進行驗證、替換、切分、提取這四種操作。

2.1 驗證

驗證應該是前端程式設計師寫正規表示式用的最多的方法吧,比如表單驗證之類的。可以實現驗證的方法有4種。

光說不練假把式,光練不說傻把式,又練又說才是真把式?。那麼,請看下面的例子?:

2.1.1 使用test方法

如果正規表示式與指定的字串匹配 ,返回true,否則false。得到的結果可以直接使用。

const str = 'jing ke tong xue 666';

let reg1 = /\d{4}/;
let res1 = reg1.test(str);
console.log(res1);
// => false

let reg2 = /ke/;
let res2 = reg2.test(str);
console.log(res2);
// => true

let reg3 = /\d{3}/;
let res3 = reg3.test(str);
console.log(res3);
// => true
複製程式碼

下面我們看一個匹配身份證的示例:

const str1 = '411199909096896';
const str2 = '411425199909096896';
const str3 = '41142519990909689x';
const str4 = '41142519990909689X';
const reg = /^(\d{15}|\d{17}[\dxX])$/;
let res1 = reg.test(str1);
let res2 = reg.test(str2);
let res3 = reg.test(str3);
let res4 = reg.test(str4);
console.log(res1, res2, res3, res4);
// => true true true true
複製程式碼

不過好在網上有很多視覺化工具供我們使用,看一下下圖可能就豁然開朗。

視覺化檢視

看圖分析:這裡豎槓|的優先順序最低,所以正則分成了兩部分\d{15}\d{17}[\dxX]

  • \d{15} 表示15位連續數字

  • \d{17}[\dxX] 表示17位連續數字,最後一位可以是數字,也可以大寫或小寫字母 "x"

下面我們再看一個稍微複雜一點的案例,匹配IPV4地址:

const str = '192.168.0.1';
let reg = /^((0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])\.){3}(0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])$/;
let res = reg.test(str);
console.log(res);
// => true
複製程式碼

上面那個正規表示式初看起來有點嚇人哈?,這就是傳說中的寫正則不難,讀正則難。先看下面視覺化檢視:

視覺化檢視

此正規表示式主體結構大致如下:

((…)\.){3}(…)

兩個(...)是相同的內容,因此文字描述為3位數字.3數字.3位數字.3位數字

下面我們具體分析(...)也就是(0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5]),單獨看一下這段程式碼的視覺化檢視:

視覺化檢視

看圖分析,它是一個多選結構,分成5個部分:

  • 0{0,2}\d: 匹配一位數,包括 "0" 補齊的。比如,"9"、"09"、"009"

  • 0?\d{2}: 匹配兩位數,包括 "0" 補齊的,也包括一位數

  • 1\d{2}: 匹配 "100" 到 "199"

  • 2[0-4]\d: 匹配 "200" 到 "249"

  • 25[0-5]: 匹配 "250" 到 "255"

不要被看起來複雜的正規表示式嚇到,乍看起來複雜的不要不要的,那我們就把它像洋蔥一樣,一層一層剝開它的心,擁抱它,然後扒光它。

2.1.2 使用exec方法

如果匹配成功,返回一個Array,否則返回null。得到的結果兩次取反取得true或者false使用。

const str = 'jing ke tong xue 666';

let reg1 = /\d{4}/;
let res1 = reg1.exec(str);
console.log(res1);
console.log(!!res1);
// => null
// => false

let reg2 = /ke/;
let res2 = reg2.exec(str);
console.log(res2);
console.log(!!res2);
// => ["ke", index: 5, input: "jing ke tong xue 666", groups: undefined]
// => true

let reg3 = /\d{3}/;
let res3 = reg3.exec(str);
console.log(res3);
console.log(!!res3);
// => ["666", index: 17, input: "jing ke tong xue 666", groups: undefined]
// => true
複製程式碼
2.1.3 使用search方法

如果匹配成功,返回正規表示式在字串中首次匹配項的索引(大於等於0),否則,返回 -1。如果為了和上面的方法保持一致返回true或false,這裡需要藉助一次按位非操作符(~)。

const str = 'jing ke tong xue 666';

let reg1 = /\d{4}/;
let res1 = str.search(reg1);
console.log(res1);
console.log(!!~res1);
// => -1
// => flase

let reg2 = /ke/;
let res2 = str.search(reg2);
console.log(res2);
console.log(!!~res2);
// => 5
// => true

const reg3 = /\d{3}/;
let res3 = str.search(reg3);
console.log(res3);
console.log(!!~res3);
// => 17
// => true
複製程式碼
2.1.4 使用match方法

如果匹配成功,返回一個Array,否則返回null。得到的結果兩次取反取得true或者false使用。

const str = 'jing ke tong xue 666';

let reg1 = /\d{4}/;
let res1 = str.match(reg1);
console.log(res1);
console.log(!!res1);
// => null
// => flase

let reg2 = /ke/;
let res2 = str.match(reg2);
console.log(res2);
console.log(!!res2);
// => ["ke", index: 5, input: "jing ke tong xue 666", groups: undefined]
// => true

let reg3 = /\d{3}/;
let res3 = str.match(reg3);
console.log(res3);
console.log(!!res3);
// => ["666", index: 17, input: "jing ke tong xue 666", groups: undefined]
// => true
複製程式碼

2.2 替換

有時候找到了物件往往不是我們最終的目的,找到了物件做點什麼才是我們想要的?。

這裡我們看一看找到後的替換操作,我本人的常用場景就是格式化日期和刪除空格。

// 把YYYY/MM/DD格式的日期替換成YYYY-MM-DD格式。
const str1 = 'jing-ke-tong-xue';

let res1 = str1.replace(/-/g, ' ');
console.log(res1);
// => jing ke tong xue

// 刪除前後空格, 為了直觀這裡把前後空格替換成“刪除了的空格”
const str2 = ' jing ke tong xue ';
let res2 = str2.replace(/^\s|\s$/g, '刪除了的空格');
console.log(res2);
// => 刪除了的空格jing ke tong xue刪除了的空格

// 據說下面這種方法速度比較快
const str3 = ' jing ke tong xue ';
let res3 = str3.replace(/^\s/, '刪除了的空格').replace(/\s$/, '刪除了的空格');
console.log(res3);
// => 刪除了的空格jing ke tong xue刪除了的空格
複製程式碼

2.3 切分

切蘿蔔切蘿蔔切切切,包餃子包餃子捏捏捏?。在這裡,所謂切分就是把字串切的一段一段的。

// 按空格切分
const str = 'jing ke tong xue';

console.log(str.split(/\s/));
// => ["jing", "ke", "tong", "xue"]

const str2 = 'jing * ke ¥ tong ^xue';
console.log(str2.split(/\s\*\s|\s¥\s|\s\^/))
// => ["jing", "ke", "tong", "xue"]
複製程式碼

2.4 提取

有時候驗證、替換、切分都不是目的,我們需要提取出來對我們有用的資訊,那麼就需要提取了。此時需要用到下篇會寫到的“括號”(分組引用或者分組捕獲)。

這裡有一個正則將會貫穿本小節,我們先提前分析一下:

// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg = /(\d{4})\D(\d{2})\D(\d{2})/g;
複製程式碼

視覺化檢視:

視覺化檢視

看圖說話:從上圖可知這段正則的意思就是:4個數字後跟一個非數字再跟2個數字再跟一個非數字再跟2個數字。形如:4個數字-2個數字-2個數字格式。

2.4.1 使用exec方法

const str = "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串";
// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg = /(\d{4})\D(\d{2})\D(\d{2})/g;
// 上篇文章有說:`exex`方法匹配到一次就會返回結果,想要下一個結果必須再次呼叫
console.log(reg.exec(str));
console.log(reg.exec(str));
// => ["2018-04-04", "2018", "04", "04", index: 16, input: "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是2018-40-40,提取日期測試字串", groups: undefined]
// => ["6666-66-66", "6666", "66", "66", index: 34, input: "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串", groups: undefined]
複製程式碼

2.4.2 使用match方法

const str = "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串";

// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg1 = /(\d{4})\D(\d{2})\D(\d{2})/;
console.log(str.match(reg1));
// 沒有g識別符號返回結果與match無異
// => ["2018-04-04", "2018", "04", "04", index: 16, input: "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串", groups: undefined]

// 帶有g識別符號,結果陣列只包含匹配結果
// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg2 = /(\d{4})\D(\d{2})\D(\d{2})/g;
console.log(str.match(reg2));
// => ["2018-04-04", "6666-66-66"]
複製程式碼

2.4.3 使用replace方法

在上篇文章步《JavaScript正規表示式學習筆記(一) - 理論基礎》的理論基礎中提到replace方法的第二個引數可以是一個函式,函式接收的引數包含我們需要的資訊。

const str = "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串";

// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg1 = /(\d{4})\D(\d{2})\D(\d{2})/;

let res1 = [];
str.replace(reg1, function(match, year, month, day, offset, string) {
    res1.push(match, year, month, day, offset, string);
});
console.log(res1);
// => ["2018-04-04", "2018", "04", "04", 16, "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串"]

// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg2 = /(\d{4})\D(\d{2})\D(\d{2})/g;

let res2 = [];
str.replace(reg2, function(match, year, month, day, offset, string) {
    res2.push(match);
});
console.log(res2);
// => ["2018-04-04", "6666-66-66"]
複製程式碼

從輸出效果來看,replace方法可以達到模擬match方法的效果。這就是傳說中的***條條大路通羅馬***No roads can't lead to Rome(哼...哼哼...翻譯是我故意的嗷?)

2.4.4 使用test方法

此方法會用到RegExp.$1-$9這一非標屬性,這裡只是使用,下文會做介紹。

const str = "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串";

// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg = /(\d{4})\D(\d{2})\D(\d{2})/;

reg.test(str);

// RegExp.$1-$9非標屬性,但是目的達到了,請勿用於生產環境
let res = [RegExp.$1, RegExp.$2, RegExp.$3];

console.log(res);
// => ["2018", "04", "04"]
複製程式碼

2.4.4 使用search方法

此方法會用到RegExp.$1-$9這一非標屬性,這裡只是使用,同樣留到下文介紹。

const str = "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串";

// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg = /(\d{4})\D(\d{2})\D(\d{2})/;

str.search(reg);

// RegExp.$1-$9非標屬性,但是目的達到了,請勿用於生產環境
let res = [RegExp.$1, RegExp.$2, RegExp.$3];

console.log(res);
// => ["2018", "04", "04"]
複製程式碼

到這裡正規表示式的驗證、替換、切分、提取已經介紹完了,有些操作是取巧的做法,也不建議在生產環境使用,不過某些特殊情況除外。至於哪些情況是特殊情況,具體問題具體分析吧?。

三. 注意要點

在本文 1.2 小節提到了6種可以用於正則操作的方法,RegExp提供2種,String提供4種。本章節就圍繞這幾種方法展開。

3.1 match方法返回結果的格式不一致問題

這個問題上文《JavaScript正規表示式學習筆記(一) - 理論基礎》也有體現,這裡再單獨拿來說一說,以加深記憶。

const str = "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串";

// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg1 = /(\d{4})\D(\d{2})\D(\d{2})/;
console.log(str.match(reg1));
// => ["2018-04-04", "2018", "04", "04", index: 16, input: "提取日期測試字串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測試字串", groups: undefined]

// 這裡為了簡單測試,只考慮了日期格式,沒考慮日期的合理性
let reg2 = /(\d{4})\D(\d{2})\D(\d{2})/g;
console.log(str.match(reg2));
// => ["2018-04-04", "6666-66-66"]

// 這個正則只是來客串說明問題,沒有其他意義
let reg3 = /.^/;
console.log(str.match(reg3));
// => null

// 這個正則只是來客串說明問題,沒有其他意義
let reg4 = /.^/g;
console.log(str.match(reg4));
// => null
複製程式碼
  • 當沒有g識別符號時,返回的結果為標準匹配格式,包含完整的匹配資訊。

  • 當有g識別符號時,返回的結果為所有匹配結果組成的陣列。

  • 當匹配不成功時,無論有沒有識別符號(包括igmyu的任意組合),都返回null

3.2 searchmatch的引數問題

話不多說,先看程式碼:

// 為了說明問題,日期格式選擇了用“.”連線,因為“.”在正則中屬於元字元
const str = '2018.04.04';

console.log(str.search('.'));
console.log(str.search(/./));
console.log(str.search('\\.'));
console.log(str.search(/\./));
// => 0
// => 0
// => 4
// => 4

console.log(str.match('.'));
console.log(str.match(/./));
console.log(str.match('\\.'));
console.log(str.match(/\./));
// => ["2", index: 0, input: "2018.04.04", groups: undefined]
// => ["2", index: 0, input: "2018.04.04", groups: undefined]
// => [".", index: 4, input: "2018.04.04", groups: undefined]
// => [".", index: 4, input: "2018.04.04", groups: undefined]

console.log(str.replace('.', '-'));
console.log(str.replace( /./, '-'));
console.log(str.replace('\.', '-'));
console.log(str.replace(/\./, '-'));
// => 2018-04.04
// => -018.04.04
// => 2018-04.04
// => 2018-04.04

console.log(str.split('.'));
console.log(str.split(/./));
console.log(str.split('\\.'));
console.log(str.split(/\./));
// => ["2018", "04", "04"]
// => ["", "", "", "", "", "", "", "", "", "", ""]
// => ["2018.04.04"]
// => ["2018", "04", "04"]
複製程式碼

從上述程式碼可以看出search方法和match方法會將接收到的字串引數轉為正則,而replace方法和split方法不會轉換,所以使用時請注意。

3.3 execmatch的王者之爭

王者之爭第一回合:

const str = '2018-04-04';

let reg1 = /\b(\d+)\b/;
let reg2 = /\b(\d+)\b/g;

console.log(reg1.exec(str));
console.log(reg2.exec(str));
// => ["2018", "2018", index: 0, input: "2018-04-04", groups: undefined]
// => ["2018", "2018", index: 0, input: "2018-04-04", groups: undefined]

console.log(str.match(reg1));
console.log(str.match(reg2));
// => ["2018", "2018", index: 0, input: "2018-04-04", groups: undefined]
// ["2018", "04", "04"]
複製程式碼
  • g識別符號對exec方法沒有產生影響,但是改變了match方法的行為。沒主見啊,沒主見。

  • 沒有g識別符號時,match返回標準匹配引數,有g識別符號時返回了匹配結果的集合。但是對於exec方法無論有沒有g識別符號都返回了同樣的結果。不智慧啊,不智慧。

這一回合實力相差不大,看不出孰優孰劣。

王者之爭第二回合:

const str = '2018-04-04';

let reg1 = /\b(\d+)\b/;
let reg2 = /\b(\d+)\b/g;

console.log(reg2.lastIndex);
console.log(reg2.exec(str));
// => 0
// => ["2018", "2018", index: 0, input: "2018-04-04", groups: undefined]

console.log(reg2.lastIndex);
console.log(reg2.exec(str));
// => 4
// => ["04", "04", index: 5, input: "2018-04-04", groups: undefined]

console.log(reg2.lastIndex);
console.log(reg2.exec(str));
// => 7
// => ["04", "04", index: 8, input: "2018-04-04", groups: undefined]

console.log(reg2.lastIndex);
console.log(reg2.exec(str));
// => 10
// => null

// 上一次匹配失敗,這一次從頭開始
console.log(reg2.lastIndex);
console.log(reg2.exec(str));
// => 0
// => ["2018", "2018", index: 0, input: "2018-04-04", groups: undefined]

// 沒有g識別符號時match方法每次都從第一位開始匹配
console.log(str.match(reg1));
console.log(str.match(reg1));
// => ["2018", "2018", index: 0, input: "2018-04-04", groups: undefined]
// => ["2018", "2018", index: 0, input: "2018-04-04", groups: undefined]
複製程式碼

這一回合沒有懸念,exec方法勝出。不過上面的寫法未免太過繁瑣。請看下面當exec遇上while

const str = '2018-04-04';

let reg = /\b(\d+)\b/g;

// 結合while流程控制語句
let res;
while (res = reg.exec(str)) {
    console.log(reg.lastIndex, res);
}
// => 4 ["2018", "2018", index: 0, input: "2018-04-04", groups: undefined]
// => 7 ["04", "04", index: 5, input: "2018-04-04", groups: undefined]
// => 10 ["04", "04", index: 8, input: "2018-04-04", groups: undefined]
複製程式碼

王者之爭至此結束。exec要比match強大那麼一點哈。

3.4 除了exec方法,g識別符號對test方法也有影響

上篇文章曾經提到 lastIndex 是正規表示式的一個可讀可寫的整型屬性,用來指定下一次匹配的起始索引。 也就是說,只要正則還是那個正則,當存在g識別符號的時候lastIndex都會做出相應的改變,要匹配的字串可以不是同一個。

const str1 = '2018-04-04';
const str2 = '2018-04-05';
const str3 = '2018-04-06';
const str4 = '2018-04-07';
const str5 = '2018-04-08';

let reg = /\b(\d+)\b/g;

console.log(reg.lastIndex);
console.log(reg.test(str1));
// => 0
// => true

console.log(reg.lastIndex);
console.log(reg.test(str2));
// => 4
// => true

console.log(reg.lastIndex);
console.log(reg.test(str3));
// => 7
// => true

console.log(reg.lastIndex);
console.log(reg.test(str4));
// => 10
// => false

console.log(reg.lastIndex);
console.log(reg.test(str5));
// => 0
// => true
複製程式碼

當沒有g操作符時,始終從0開始匹配,這裡就不做演示了。

3.5 split方法注意事項

const str = '2018-04-04';

console.log(str.split('-'));
console.log(str.split('-', 2));
console.log(str.split('-', 10));
// => ["2018", "04", "04"]
// => ["2018", "04"]
// => ["2018", "04", "04"]

let reg1 = /-/;
let reg2 = /(-)/;

console.log(str.split(reg1));
console.log(str.split(reg2));
// => ["2018", "04", "04"]
// => ["2018", "-", "04", "-", "04"]
複製程式碼
  • split方法可以接收第二個引數指定返回陣列長度,第二個引數只有小於實際返回陣列長度時才生效。

  • split接收的正規表示式種包含分組模式時,返回的結果陣列包含分組匹配項。

3.6 強大的replace方法

replace方法不但可以接收一個函式作為第二個引數(前面已經體現,這兒不重複示例),也可以接收一個字串作為第二個引數。此處的字串除了是一個普通的替換字串之外,也可以是一個特殊變數。本小節以實際示例介紹一下這幾個特殊變數(第一篇理論基礎有提及)。

變數名 代表的值
$$ 插入一個 "$"。
$& 插入匹配的子串。
$` 插入當前匹配的子串左邊的內容。
$' 插入當前匹配的子串右邊的內容。
$n 匹配第n個分組裡捕獲的文字,n是不大於100的正整數。

哇哈,是時候表演真正的技術了,下面我們就來看看replace的能力。

const str1 = '3 6 9';
        
let reg1 = /\d/g;
// 被$包圍
console.log(str1.replace(reg1, '$$$&$$'));
// => $3$ $6$ $9$

// 分身
console.log(str1.replace(reg1, '$&$&$&'));
// => 333 666 999

// 分身相加
let reg2 = /(\d)\s(\d)\s(\d)/;
console.log(str1.replace(reg2, '$1$1$1+$2$2$2$2=$3$3$3'));
// => 333+6666=999

// 你愛我我愛你
console.log(str1.replace(reg2, '$1$1$1+$2$2$2=$2$2$2+$1$1$1=$3$3$3=>?'));
// => 333+666=666+333=999=>?

const str2 = '3?6?9';
let reg3 = /?|?/g;
console.log(str2.replace(reg3, "($&的左邊是: $`, 右邊是: $')"));
// => 3(?的左邊是: 3, 右邊是: 6?9)6(?的左邊是: 3?6, 右邊是: 9)9
複製程式碼

突然間感覺王者之爭不應該只讓execmatch參加,replace這麼強大,也應該參與其中的嘛?。

3.7 建構函式和字面量問題

這裡沒有什麼懸念,一般建議優先使用字面量的方式建立正規表示式,因為建構函式中需要對元字元轉義,會多寫很多的反斜槓。當然特殊情況還是要用建構函式。

const str = '2018-04-04';

let reg1 = /(\d{4})\D(\d{2})\D(\d{2})/;
console.log(reg1);
console.log(reg1.test(str));
// => /(\d{4})\D(\d{2})\D(\d{2})/
// => true

let reg2 = new RegExp('(\d{4})\D(\d{2})\D(\d{2})');
console.log(reg2);
console.log(reg2.test(str));
// => /(d{4})D(d{2})D(d{2})/
// => false

let reg3 = new RegExp('(\\d{4})\\D(\\d{2})\\D(\\d{2})');
console.log(reg3);
console.log(reg3.test(str));
// => /(\d{4})\D(\d{2})\D(\d{2})/
// => true
複製程式碼

下面是特殊情況:

let name = 'user name';

// user name是一個變數
const str = '2018-04-04 user name';

// 在字面量中,無法實現動態拼接
let reg1 = /(\d{4})\D(\d{2})\D(\d{2})\D + name/;
console.log(reg1);
console.log(reg1.test(str));
// => /(\d{4})\D(\d{2})\D(\d{2})\D + name/
// => flase

let reg2 = new RegExp('(\\d{4})\\D(\\d{2})\\D(\\d{2})\\D' + '(' + name + ')');
console.log(reg2);
console.log(reg2.test(str));
// => /(\d{4})\D(\d{2})\D(\d{2})\D(user name)/
// => true
複製程式碼

3.8 幾個非標屬性

上面用到了RegExp.$1這一非標屬性,所謂非標屬性,就是此屬性不符合當前任何標準規範。所以,請儘量不要在生產環境中使用,除非特殊情況並且你能保證以後也不會出錯。

屬性 別名 說明
RegExp.$1-$9 靜態、只讀屬性。包含括號子串匹配的正規表示式的靜態和只讀屬性。只有在正確匹配的情況下才會改變。雖然括號可以無限,但是此屬性最多隻能匹配9個。
RegExp.input RegExp.$_ 靜態屬性,含有正規表示式最近一次所匹配的字串。當正規表示式上搜尋的字串發生該變,並且字串匹配時,input 屬性的值會修改。
RegExp.lastMatch RegExp['$&'] 靜態、只讀屬性。含有最近一次匹配到的字串。屬性的值是隻讀的,會在匹配成功時修改。
RegExp.lastParen RegExp['$+'] 靜態、只讀屬性,包含匹配到的最後一個子串。會在匹配成功時修改。
RegExp.leftContext RegExp['$`'] 靜態、只讀屬性。含有最新匹配的左側子串。 會在匹配成功時修改。
RegExp.rightContext RegExp["$'"] 靜態、只讀屬性。含有最新匹配的右側子串。 會在匹配成功時修改。

這幾個屬性平時也基本用不到,瞭解瞭解總是好的,請看下面示例:

const str = 'a1b2c3d4e5f6';
let reg = /([a-f])([1-6])/g;

// 為了倒數第二個有輸出,這裡執行兩次exec方法
console.log(reg.exec(str));
console.log(reg.exec(str));
// ["a1", "a", "1", index: 0, input: "a1b2c3d4e5f6", groups: undefined]
// ["b2", "b", "2", index: 2, input: "a1b2c3d4e5f6", groups: undefined]

console.log(RegExp.$1);
console.log(RegExp.$2);
// => b
// => 2

console.log(RegExp.input);
console.log(RegExp.$_);
// => a1b2c3d4e5f6
// => a1b2c3d4e5f6

console.log(RegExp.lastMatch);
console.log(RegExp['$&']);
// => b2
// => b2

console.log(RegExp.lastParen);
console.log(RegExp['$+']);
// => 2
// => 2

console.log(RegExp.leftContext);
console.log(RegExp['$`']);
// => a1
// => a1

console.log(RegExp.rightContext);
console.log(RegExp["$'"]);
// => c3d4e5f6
// => c3d4e5f6
複製程式碼

四. 奇技淫巧

本文寫的也挺長的,剩下的準備再寫一篇終結JavaScript正規表示式部分的內容。那麼本文就先用一個真是案例來做結尾吧。

JavaScript常用的型別判斷實現

下面這段程式碼是在某個框架原始碼中見到的,初見之時倍感驚豔(原諒我入行不久見識短?),其中也用到上文提到的split方法,特拿出來分享一下。

let utils = Object.create(null);
const types = 'Boolean|Number|String|Function|Array|Date|RegExp|Object|Error';
types.split('|').forEach(type => {
    utils['is' + type] = obj => {
        return Object.prototype.toString.call(obj) == '[object ' + type + ']';
    };
});
console.log(utils.isBoolean('true'));
console.log(utils.isBoolean(true));
複製程式碼

雖然可以將Boolean|Number|String|Function|Array|Date|RegExp|Object|Error儲存為陣列減少一次split切分操作,但是這樣似乎多了點黑科技的感覺。

由於本同學能力有限,不足之處還望各位大佬同學指正。

至此,本文完。

相關文章