JavaScript正則表達(dá)式學(xué)習(xí)筆記(二) - 打怪升級(jí)

JavaScript正則表達(dá)式學(xué)習(xí)筆記(二) - 打怪升級(jí).png

本文接上篇,基礎(chǔ)部分相對(duì)薄弱的同學(xué)請(qǐng)移步《JavaScript正則表達(dá)式學(xué)習(xí)筆記(一) - 理論基礎(chǔ)》。上文介紹了8種JavaScript正則表達(dá)式的屬性,本文還會(huì)追加介紹幾種JavaScript正則表達(dá)式的屬性(注意是非標(biāo)準(zhǔn)屬性,但很好用)。

一. 上文回顧

本文會(huì)用到上篇文章部分內(nèi)容,所以簡(jiǎn)單回顧下。

1.1 JavaScript正則表達(dá)式標(biāo)志符

  1. g: 全局匹配,即找到所有匹配的。對(duì)應(yīng)屬性RegExp#global。

  2. i: 忽略字母大小寫。對(duì)應(yīng)屬性RegExp#ingoreCase

  3. m: 多行匹配,只影響^和$,二者變成行的概念,即行開頭和行結(jié)尾。對(duì)應(yīng)屬性RegExp#multiline。

  4. u: ES6新增。含義為“Unicode 模式”,用來(lái)正確處理大于\uFFFF的 Unicode 字符。也就是說(shuō),會(huì)正確處理四個(gè)字節(jié)的 UTF-16 編碼。對(duì)應(yīng)屬性RegExp#unicode。

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

1.2 適用于Javascript正則表達(dá)式的方法

上篇文章《JavaScript正則表達(dá)式學(xué)習(xí)筆記(一) - 理論基礎(chǔ)》介紹了適用于JavaScript正則表達(dá)式模式匹配的相關(guān)API共有6種,RexExp提供2個(gè),String提供4個(gè),如下:

1. RegExp#test    // 適用于:驗(yàn)證、提取
2. RegExp#exec    // 適用于:驗(yàn)證、提取
3. String#search  // 適用于:驗(yàn)證、提取
4. String#match   // 適用于:驗(yàn)證、提取
5. String#split   // 適用于:切分
6. String#replace // 適用于:提取、替換

二. JavaScript正則表達(dá)式的四種操作

正則表達(dá)式是用于匹配字符串中字符組合的模式, 其核心內(nèi)容便是模式匹配。也就是說(shuō),不論進(jìn)行那種操作,首先要有模式匹配,有了模式匹配之后,才能進(jìn)行驗(yàn)證、替換、切分、提取這四種操作。

2.1 驗(yàn)證

驗(yàn)證應(yīng)該是前端程序員寫正則表達(dá)式用的最多的方法吧,比如表單驗(yàn)證之類的??梢詫?shí)現(xiàn)驗(yàn)證的方法有4種。

光說(shuō)不練假把式,光練不說(shuō)傻把式,又練又說(shuō)才是真把式??。那么,請(qǐng)看下面的例子??:

2.1.1 使用test方法

如果正則表達(dá)式與指定的字符串匹配 ,返回true,否則false。得到的結(jié)果可以直接使用。

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

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

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

不過(guò)好在網(wǎng)上有很多可視化工具供我們使用,看一下下圖可能就豁然開朗。

可視化視圖

看圖分析:這里豎杠|的優(yōu)先級(jí)最低,所以正則分成了兩部分\d{15}\d{17}[\dxX]

  • \d{15} 表示15位連續(xù)數(shù)字

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

下面我們?cè)倏匆粋€(gè)稍微復(fù)雜一點(diǎn)的案例,匹配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

上面那個(gè)正則表達(dá)式初看起來(lái)有點(diǎn)嚇人哈??,這就是傳說(shuō)中的寫正則不難,讀正則難。先看下面可視化視圖:

可視化視圖.png

此正則表達(dá)式主體結(jié)構(gòu)大致如下:

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

兩個(gè)(...)是相同的內(nèi)容,因此文字描述為3位數(shù)字.3數(shù)字.3位數(shù)字.3位數(shù)字。

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

可視化視圖.png

看圖分析,它是一個(gè)多選結(jié)構(gòu),分成5個(gè)部分:

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

  • 0?\d{2}: 匹配兩位數(shù),包括 "0" 補(bǔ)齊的,也包括一位數(shù)

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

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

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

不要被看起來(lái)復(fù)雜的正則表達(dá)式嚇到,乍看起來(lái)復(fù)雜的不要不要的,那我們就把它像洋蔥一樣,一層一層剝開它的心,擁抱它,然后扒光它。

2.1.2 使用exec方法

如果匹配成功,返回一個(gè)Array,否則返回null。得到的結(jié)果兩次取反取得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方法

如果匹配成功,返回正則表達(dá)式在字符串中首次匹配項(xiàng)的索引(大于等于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方法

如果匹配成功,返回一個(gè)Array,否則返回null。得到的結(jié)果兩次取反取得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 替換

有時(shí)候找到了對(duì)象往往不是我們最終的目的,找到了對(duì)象做點(diǎn)什么才是我們想要的??。

這里我們看一看找到后的替換操作,我本人的常用場(chǎng)景就是格式化日期和刪除空格。

// 把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刪除了的空格

// 據(jù)說(shuō)下面這種方法速度比較快
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 提取

有時(shí)候驗(yàn)證、替換、切分都不是目的,我們需要提取出來(lái)對(duì)我們有用的信息,那么就需要提取了。此時(shí)需要用到下篇會(huì)寫到的“括號(hào)”(分組引用或者分組捕獲)。

這里有一個(gè)正則將會(huì)貫穿本小節(jié),我們先提前分析一下:

// 這里為了簡(jiǎn)單測(cè)試,只考慮了日期格式,沒(méi)考慮日期的合理性
let reg = /(\d{4})\D(\d{2})\D(\d{2})/g;

可視化視圖:

可視化視圖.png

看圖說(shuō)話:從上圖可知這段正則的意思就是:4個(gè)數(shù)字后跟一個(gè)非數(shù)字再跟2個(gè)數(shù)字再跟一個(gè)非數(shù)字再跟2個(gè)數(shù)字。形如:4個(gè)數(shù)字-2個(gè)數(shù)字-2個(gè)數(shù)字格式。

2.4.1 使用exec方法

const str = "提取日期測(cè)試字符串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測(cè)試字符串";
// 這里為了簡(jiǎn)單測(cè)試,只考慮了日期格式,沒(méi)考慮日期的合理性
let reg = /(\d{4})\D(\d{2})\D(\d{2})/g;
// 上篇文章有說(shuō):`exex`方法匹配到一次就會(huì)返回結(jié)果,想要下一個(gè)結(jié)果必須再次調(diào)用
console.log(reg.exec(str));
console.log(reg.exec(str));
// => ["2018-04-04", "2018", "04", "04", index: 16, input: "提取日期測(cè)試字符串,今天的日期是2018-04-04,今天的日期不是2018-40-40,提取日期測(cè)試字符串", groups: undefined]
// => ["6666-66-66", "6666", "66", "66", index: 34, input: "提取日期測(cè)試字符串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測(cè)試字符串", groups: undefined]

2.4.2 使用match方法

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

// 這里為了簡(jiǎn)單測(cè)試,只考慮了日期格式,沒(méi)考慮日期的合理性
let reg1 = /(\d{4})\D(\d{2})\D(\d{2})/;
console.log(str.match(reg1));
// 沒(méi)有g(shù)標(biāo)識(shí)符返回結(jié)果與match無(wú)異
// => ["2018-04-04", "2018", "04", "04", index: 16, input: "提取日期測(cè)試字符串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測(cè)試字符串", groups: undefined]

// 帶有g(shù)標(biāo)識(shí)符,結(jié)果數(shù)組只包含匹配結(jié)果
// 這里為了簡(jiǎn)單測(cè)試,只考慮了日期格式,沒(méi)考慮日期的合理性
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正則表達(dá)式學(xué)習(xí)筆記(一) - 理論基礎(chǔ)》的理論基礎(chǔ)中提到replace方法的第二個(gè)參數(shù)可以是一個(gè)函數(shù),函數(shù)接收的參數(shù)包含我們需要的信息。

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

// 這里為了簡(jiǎn)單測(cè)試,只考慮了日期格式,沒(méi)考慮日期的合理性
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, "提取日期測(cè)試字符串,今天的日期是2018-04-04,今天的日期不是6666-66-66,提取日期測(cè)試字符串"]

// 這里為了簡(jiǎn)單測(cè)試,只考慮了日期格式,沒(méi)考慮日期的合理性
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"]

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

2.4.4 使用test方法

此方法會(huì)用到RegExp.$1-$9這一非標(biāo)屬性,這里只是使用,下文會(huì)做介紹。

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

// 這里為了簡(jiǎn)單測(cè)試,只考慮了日期格式,沒(méi)考慮日期的合理性
let reg = /(\d{4})\D(\d{2})\D(\d{2})/;

reg.test(str);

// RegExp.$1-$9非標(biāo)屬性,但是目的達(dá)到了,請(qǐng)勿用于生產(chǎn)環(huán)境
let res = [RegExp.$1, RegExp.$2, RegExp.$3];

console.log(res);
// => ["2018", "04", "04"]

2.4.4 使用search方法

此方法會(huì)用到RegExp.$1-$9這一非標(biāo)屬性,這里只是使用,同樣留到下文介紹。

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

// 這里為了簡(jiǎn)單測(cè)試,只考慮了日期格式,沒(méi)考慮日期的合理性
let reg = /(\d{4})\D(\d{2})\D(\d{2})/;

str.search(reg);

// RegExp.$1-$9非標(biāo)屬性,但是目的達(dá)到了,請(qǐng)勿用于生產(chǎn)環(huán)境
let res = [RegExp.$1, RegExp.$2, RegExp.$3];

console.log(res);
// => ["2018", "04", "04"]

到這里正則表達(dá)式的驗(yàn)證、替換、切分、提取已經(jīng)介紹完了,有些操作是取巧的做法,也不建議在生產(chǎn)環(huán)境使用,不過(guò)某些特殊情況除外。至于哪些情況是特殊情況,具體問(wèn)題具體分析吧??。

三. 注意要點(diǎn)

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

3.1 match方法返回結(jié)果的格式不一致問(wèn)題

這個(gè)問(wèn)題上文《JavaScript正則表達(dá)式學(xué)習(xí)筆記(一) - 理論基礎(chǔ)》也有體現(xiàn),這里再單獨(dú)拿來(lái)說(shuō)一說(shuō),以加深記憶。

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

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

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

// 這個(gè)正則只是來(lái)客串說(shuō)明問(wèn)題,沒(méi)有其他意義
let reg3 = /.^/;
console.log(str.match(reg3));
// => null

// 這個(gè)正則只是來(lái)客串說(shuō)明問(wèn)題,沒(méi)有其他意義
let reg4 = /.^/g;
console.log(str.match(reg4));
// => null
  • 當(dāng)沒(méi)有g標(biāo)識(shí)符時(shí),返回的結(jié)果為標(biāo)準(zhǔn)匹配格式,包含完整的匹配信息。

  • 當(dāng)有g標(biāo)識(shí)符時(shí),返回的結(jié)果為所有匹配結(jié)果組成的數(shù)組。

  • 當(dāng)匹配不成功時(shí),無(wú)論有沒(méi)有標(biāo)識(shí)符(包括igmyu的任意組合),都返回null。

3.2 searchmatch的參數(shù)問(wèn)題

話不多說(shuō),先看代碼:

// 為了說(shuō)明問(wèn)題,日期格式選擇了用“.”連接,因?yàn)椤?”在正則中屬于元字符
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方法會(huì)將接收到的字符串參數(shù)轉(zhuǎn)為正則,而replace方法和split方法不會(huì)轉(zhuǎn)換,所以使用時(shí)請(qǐng)注意。

3.3 execmatch的王者之爭(zhēng)

王者之爭(zhēng)第一回合:

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標(biāo)識(shí)符對(duì)exec方法沒(méi)有產(chǎn)生影響,但是改變了match方法的行為。沒(méi)主見啊,沒(méi)主見。

  • 沒(méi)有g標(biāo)識(shí)符時(shí),match返回標(biāo)準(zhǔn)匹配參數(shù),有g標(biāo)識(shí)符時(shí)返回了匹配結(jié)果的集合。但是對(duì)于exec方法無(wú)論有沒(méi)有g標(biāo)識(shí)符都返回了同樣的結(jié)果。不智能啊,不智能。

這一回合實(shí)力相差不大,看不出孰優(yōu)孰劣。

王者之爭(zhēng)第二回合:

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]

// 沒(méi)有g(shù)標(biāo)識(shí)符時(shí)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]

這一回合沒(méi)有懸念,exec方法勝出。不過(guò)上面的寫法未免太過(guò)繁瑣。請(qǐng)看下面當(dāng)exec遇上while

const str = '2018-04-04';

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

// 結(jié)合while流程控制語(yǔ)句
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]

王者之爭(zhēng)至此結(jié)束。exec要比match強(qiáng)大那么一點(diǎn)哈。

3.4 除了exec方法,g標(biāo)識(shí)符對(duì)test方法也有影響

上篇文章曾經(jīng)提到 lastIndex 是正則表達(dá)式的一個(gè)可讀可寫的整型屬性,用來(lái)指定下一次匹配的起始索引。 也就是說(shuō),只要正則還是那個(gè)正則,當(dāng)存在g標(biāo)識(shí)符的時(shí)候lastIndex都會(huì)做出相應(yīng)的改變,要匹配的字符串可以不是同一個(gè)。

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

當(dāng)沒(méi)有g操作符時(shí),始終從0開始匹配,這里就不做演示了。

3.5 split方法注意事項(xiàng)

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方法可以接收第二個(gè)參數(shù)指定返回?cái)?shù)組長(zhǎng)度,第二個(gè)參數(shù)只有小于實(shí)際返回?cái)?shù)組長(zhǎng)度時(shí)才生效。

  • 當(dāng)split接收的正則表達(dá)式種包含分組模式時(shí),返回的結(jié)果數(shù)組包含分組匹配項(xiàng)。

3.6 強(qiáng)大的replace方法

replace方法不但可以接收一個(gè)函數(shù)作為第二個(gè)參數(shù)(前面已經(jīng)體現(xiàn),這兒不重復(fù)示例),也可以接收一個(gè)字符串作為第二個(gè)參數(shù)。此處的字符串除了是一個(gè)普通的替換字符串之外,也可以是一個(gè)特殊變量。本小節(jié)以實(shí)際示例介紹一下這幾個(gè)特殊變量(第一篇理論基礎(chǔ)有提及)。

變量名 代表的值
$$ 插入一個(gè) "$"。
$& 插入匹配的子串。
$` 插入當(dāng)前匹配的子串左邊的內(nèi)容。
$' 插入當(dāng)前匹配的子串右邊的內(nèi)容。
$n 匹配第n個(gè)分組里捕獲的文本,n是不大于100的正整數(shù)。

哇哈,是時(shí)候表演真正的技術(shù)了,下面我們就來(lái)看看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

突然間感覺(jué)王者之爭(zhēng)不應(yīng)該只讓execmatch參加,replace這么強(qiáng)大,也應(yīng)該參與其中的嘛??。

3.7 構(gòu)造函數(shù)和字面量問(wèn)題

這里沒(méi)有什么懸念,一般建議優(yōu)先使用字面量的方式創(chuàng)建正則表達(dá)式,因?yàn)闃?gòu)造函數(shù)中需要對(duì)元字符轉(zhuǎn)義,會(huì)多寫很多的反斜杠。當(dāng)然特殊情況還是要用構(gòu)造函數(shù)。

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是一個(gè)變量
const str = '2018-04-04 user name';

// 在字面量中,無(wú)法實(shí)現(xiàn)動(dòng)態(tài)拼接
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 幾個(gè)非標(biāo)屬性

上面用到了RegExp.$1這一非標(biāo)屬性,所謂非標(biāo)屬性,就是此屬性不符合當(dāng)前任何標(biāo)準(zhǔn)規(guī)范。所以,請(qǐng)盡量不要在生產(chǎn)環(huán)境中使用,除非特殊情況并且你能保證以后也不會(huì)出錯(cuò)。

屬性 別名 說(shuō)明
RegExp.$1-$9 無(wú) 靜態(tài)、只讀屬性。包含括號(hào)子串匹配的正則表達(dá)式的靜態(tài)和只讀屬性。只有在正確匹配的情況下才會(huì)改變。雖然括號(hào)可以無(wú)限,但是此屬性最多只能匹配9個(gè)。
RegExp.input RegExp.$_ 靜態(tài)屬性,含有正則表達(dá)式最近一次所匹配的字符串。當(dāng)正則表達(dá)式上搜索的字符串發(fā)生該變,并且字符串匹配時(shí),input 屬性的值會(huì)修改。
RegExp.lastMatch RegExp['$&'] 靜態(tài)、只讀屬性。含有最近一次匹配到的字符串。屬性的值是只讀的,會(huì)在匹配成功時(shí)修改。
RegExp.lastParen RegExp['$+'] 靜態(tài)、只讀屬性,包含匹配到的最后一個(gè)子串。會(huì)在匹配成功時(shí)修改。
RegExp.leftContext RegExp['$`'] 靜態(tài)、只讀屬性。含有最新匹配的左側(cè)子串。 會(huì)在匹配成功時(shí)修改。
RegExp.rightContext RegExp["$'"] 靜態(tài)、只讀屬性。含有最新匹配的右側(cè)子串。 會(huì)在匹配成功時(shí)修改。

這幾個(gè)屬性平時(shí)也基本用不到,了解了解總是好的,請(qǐng)看下面示例:

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

// 為了倒數(shù)第二個(gè)有輸出,這里執(zhí)行兩次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

四. 奇技淫巧

本文寫的也挺長(zhǎng)的,剩下的準(zhǔn)備再寫一篇終結(jié)JavaScript正則表達(dá)式部分的內(nèi)容。那么本文就先用一個(gè)真是案例來(lái)做結(jié)尾吧。

JavaScript常用的類型判斷實(shí)現(xiàn)

下面這段代碼是在某個(gè)框架源碼中見到的,初見之時(shí)倍感驚艷(原諒我入行不久見識(shí)短??),其中也用到上文提到的split方法,特拿出來(lái)分享一下。

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存儲(chǔ)為數(shù)組減少一次split切分操作,但是這樣似乎多了點(diǎn)黑科技的感覺(jué)。

由于本同學(xué)能力有限,不足之處還望各位大佬同學(xué)指正。

至此,本文完。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容