本文接上篇,基礎部分相對薄弱的同學請移步《JavaScript正規表示式學習筆記(一) - 理論基礎》。上文介紹了8種JavaScript正規表示式的屬性,本文還會追加介紹幾種JavaScript正規表示式的屬性(注意是非標準屬性,但很好用)。
一. 上文回顧
本文會用到上篇文章部分內容,所以簡單回顧下。
1.1 JavaScript正規表示式標誌符
-
g
: 全域性匹配,即找到所有匹配的。對應屬性RegExp#global
。 -
i
: 忽略字母大小寫。對應屬性RegExp#ingoreCase
。 -
m
: 多行匹配,隻影響^和$,二者變成行的概念,即行開頭和行結尾。對應屬性RegExp#multiline
。 -
u
: ES6新增。含義為“Unicode 模式”,用來正確處理大於\uFFFF的 Unicode 字元。也就是說,會正確處理四個位元組的 UTF-16 編碼。對應屬性RegExp#unicode
。 -
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 search
和match
的引數問題
話不多說,先看程式碼:
// 為了說明問題,日期格式選擇了用“.”連線,因為“.”在正則中屬於元字元
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 exec
和match
的王者之爭
王者之爭第一回合:
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
複製程式碼
突然間感覺王者之爭不應該只讓exec
和match
參加,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
切分操作,但是這樣似乎多了點黑科技的感覺。
由於本同學能力有限,不足之處還望各位大佬同學指正。
至此,本文完。
- 第一篇完結: JavaScript正規表示式學習筆記(一) - 理論基礎
- 第二篇完結(就是本文):JavaScript正規表示式學習筆記(二) - 打怪升級
- 第三篇待更:JavaScript正規表示式學習筆記(三) - 終章