JavaScript正則表達(dá)式

正則簡(jiǎn)介

參考資料

JS正則表達(dá)式完整教程(略長(zhǎng))

正則表達(dá)式(regular expression),字面意思是描述規(guī)則的表達(dá)式,這里的規(guī)則指搜索字符串(即在字符串中搜索子串)的規(guī)則,你可以用正則描述你搜索字符串子串時(shí)候的規(guī)則,通過(guò)正則搜索時(shí)候會(huì)按照描述的規(guī)則進(jìn)行匹配。

為什么我們需要搜索字符串子串(即正則匹配)呢?

  • 判斷一個(gè)字符串中是否有數(shù)字【驗(yàn)證】
  • 將字符串以數(shù)字為分隔符分成多段【切分】
  • 把“2021-12-16”中的年月日提取出來(lái)【提取】
  • 把字符串中的數(shù)字都替換成16進(jìn)制【替換】

我們?cè)谌粘I(yè)務(wù)開(kāi)發(fā)中經(jīng)常會(huì)處理字符串,而在處理字符串時(shí)候可能需要按照某種規(guī)則搜索字符串子串。\

正則語(yǔ)法

通常我們使用自然語(yǔ)言描述我們查找字符串子串的需求時(shí)候會(huì)如何表達(dá)呢?

  • abab【精確匹配】
  • 3個(gè)數(shù)字、4個(gè)字母【元字符】
  • 連續(xù)2個(gè)ab【量詞】
  • 以a開(kāi)頭、以b結(jié)尾【位置】
  • 1個(gè)1~5的數(shù)字【字符組】
  • 大小寫(xiě)無(wú)關(guān)【修飾符】
  • 盡量多匹配/盡量少匹配【貪婪/惰性】
  • 3個(gè)數(shù)字重復(fù)2次【分組反向引用】
  • 3個(gè)數(shù)字或2個(gè)字母【分支】

可以看到我們?cè)诿枋鏊阉鞯淖哟囊?guī)則時(shí)候,會(huì)有不同的需求(精確匹配、模糊匹配、重復(fù)次數(shù)、盡量多/盡量少匹配)等等,這些需求正則語(yǔ)法都支持通過(guò)特定的語(yǔ)法表達(dá)。

匹配位置和匹配字符

正則表達(dá)式不是匹配字符就是匹配位置

因此當(dāng)我們根據(jù)需求寫(xiě)正則時(shí)候,先將需求轉(zhuǎn)化為匹配字符/匹配正則的描述,然后根據(jù)描述寫(xiě)正則。

精確匹配

正則表達(dá)式中可以使用具體字符來(lái)構(gòu)造要匹配的子串

/abc/

修飾符

i(ignore)

不區(qū)分大小寫(xiě)

g(global)全局匹配

默認(rèn)正則從左到右匹配,匹配到第一個(gè)返回

增加g修飾后,會(huì)將所有匹配的都記錄下來(lái),直到字符串結(jié)束

m(multiline)多行匹配

^和$匹配行首和行位,如果不加m修飾,整個(gè)作為一行,如果加了m修飾,換行符\n分割為多行。通常使用m修飾都會(huì)配合g一起使用。

'1\n'.match(/1$/)
'1\n'.match(/1$/m)
'1\n2'.match(/^2/)
'1\n2'.match(/^2/m)

元字符、元字符轉(zhuǎn)義

元字符是擁有特殊含義的字符,每個(gè)元字符代表一類(lèi)字符(數(shù)字、字母)或者位置(開(kāi)頭、結(jié)尾、單詞邊界)

字符組

當(dāng)需要表達(dá)一個(gè)在某個(gè)集合內(nèi)的字符時(shí)候,例如“1個(gè)1~5的數(shù)字”,語(yǔ)法是中括號(hào)[1,2,3,4,5],也可以用范圍[1-5],范圍表示ASCii碼對(duì)應(yīng)的碼點(diǎn)范圍,如果范圍不對(duì)(下限在ASCii中小于上限)則會(huì)報(bào)錯(cuò)。

量詞

元字符指擁有特殊含義的字符,量詞指用來(lái)修飾字符、元字符和分組的標(biāo)志符。

元字符和量詞都是正則的重要組成部分,這是從語(yǔ)法結(jié)構(gòu)來(lái)進(jìn)行分類(lèi)的。從使用場(chǎng)景的角度,更好地劃分方式是分為匹配位置和匹配字符。

匹配位置

位置指字符之間的部分,包括開(kāi)頭和結(jié)尾。字符之間只有一個(gè)位置,位置和位置不會(huì)相鄰。

  • 開(kāi)頭^
  • 結(jié)尾$
  • 單詞邊界 \b
  • 非單詞邊界 \B
  • (?=p)后面匹配到p的位置
  • (?!p)后面沒(méi)有匹配到p的位置
  • (?<=p)前面匹配到p的位置
  • (?<!p)前面未匹配到p的位置

連續(xù)的位置匹配子表達(dá)式,在正則匹配時(shí)候是“與”的關(guān)系。

'abc'.match(/(?=b)(?<=a)/)
'abc'.match(/(?<=a)(?=b)/)

注意這個(gè)括號(hào)不是分組

'abc'.match(/(?=b)(?<=a)(b)/) // 可以看到位置沒(méi)有作為匹配子表達(dá)式

貪婪、惰性

貪婪匹配指盡可能多地匹配,默認(rèn)是貪婪匹配

'aaaa'.match(/a+/g)
'aaaa'.match(/(a+)(a+)/g) // 第一個(gè)括號(hào)先盡可能多匹配,然后后面的再匹配
'aaaa'.match(/(a+)+/g) // 括號(hào)內(nèi)量詞先貪婪匹配,然后外面量詞再匹配

惰性匹配盡可能少地匹配,在量詞后面加“?”即可實(shí)現(xiàn)惰性匹配

'aaaa'.match(/a+?/g) // ["a", "a", "a", "a"]
'aaaa'.match(/(a+?)(a+?)/g) //  ["aa", "aa"]
'aaaa'.match(/(a+?)+?/g) //  ["a", "a", "a", "a"]

分支

分支相當(dāng)于編程語(yǔ)言中的或運(yùn)算

/(a|b)c/g.test('ac') // true
/(a|b)c/g.test('bc') // true

分組

分組有很多作用(作為整體進(jìn)行匹配、緩存分組、反向引用),可以將多個(gè)子表達(dá)式打包,這樣可以讓量詞作用于多個(gè)子表達(dá)式組成的整體。

分組的使用

/(ab)+/g

正則表達(dá)式匹配到之后,會(huì)緩存匹配到的每個(gè)分組,用于引用、替換和提取

提取

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( string.match(regex) ); 
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]

\

引用

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";

string.match(regex); // 或者regex.test(string);

console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "06"
console.log(RegExp.$3); // "12"

替換

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result); 
// => "06/12/2017"


var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, function() {
    return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
});
console.log(result); 
// => "06/12/2017"

反向引用

可以在正則內(nèi)部引用前面匹配到的分組,因此叫“反向引用”。

var regex = /\d{4}(-|/|.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false

非捕獲分組

如果不希望分組被捕獲,可以使用這種語(yǔ)法

(?:p)

正則回溯原理

正則匹配的大致過(guò)程是(假定使用了g修飾符)

  1. 先取出正則的子表達(dá)式進(jìn)行匹配(默認(rèn)貪婪匹配)
  2. 如果能夠匹配上,則對(duì)下一個(gè)子表達(dá)式進(jìn)行匹配(可能是下個(gè)元字符,也可能是加上量詞,例如/(a+){2,}/g 中,“(a+)”是一個(gè)子表達(dá)式,先對(duì)其匹配,如果匹配成功則繼續(xù)對(duì)(a+){2,}進(jìn)行匹配;再比如/ab/g,先匹配a,匹配上的話(huà)繼續(xù)匹配b)
  3. 如果匹配不上,則開(kāi)始回溯,回溯到上一個(gè)狀態(tài)繼續(xù)匹配。如果回溯到第一個(gè)子表達(dá)式也沒(méi)有匹配,則從下一個(gè)字符開(kāi)始匹配。為什么需要回溯呢?因?yàn)橛行┠:ヅ淇梢杂卸喾N匹配方法,只有嘗試過(guò)所有的情況都沒(méi)有匹配成功,才能認(rèn)為匹配失敗。例如/ab{1,3}c/g.test('abbc'),{1,3}可以是1個(gè)~3個(gè)。
  4. 如果正則所有子表達(dá)式匹配完,則匹配成功,這時(shí)候如果字符串沒(méi)有結(jié)束,則繼續(xù)匹配(如果沒(méi)有g(shù)修飾符,則停止)
  5. 如果字符串結(jié)束,正則未把所有子表達(dá)式匹配完,則失敗

正則使用回溯算法嘗試所有可能情況進(jìn)行匹配?;厮葸^(guò)程可能比較消耗性能。

正則性能優(yōu)化

盡量精確

正則表達(dá)式在匹配時(shí)候,如果表達(dá)式比較模糊就會(huì)存在多個(gè)狀態(tài),就可能會(huì)出現(xiàn)很多回溯情況。因此應(yīng)該盡量精確,減少可能的狀態(tài),從而減少回溯

`"abc"de`.match(/".*?"/g) // 由于惰性匹配,因此匹配到第二個(gè)引號(hào)之前會(huì)進(jìn)行回溯。

`"abc"de`.match(/"[^"]"/g) // 沒(méi)有回溯

使用非捕獲分組

因?yàn)榉纸M可能會(huì)被緩存,如果不需要對(duì)分組進(jìn)行提取、引用、反向引用等操作,則不需要捕獲分組。

獨(dú)立出確定字符

/a+/ // bad
/aa*/ // good

減少分支匹配損耗

提取分支公共部分,可以減少回溯損耗

/^abc|^def/             // bad
/^(?:abc|def)/      // good
  
  
/red|read/              // bad
/rea?d/                     // good

閱讀正則

正則語(yǔ)法結(jié)構(gòu)中的組成元素。

字符字面量、字符組、量詞、錨字符、分組、選擇分支、反向引用。

在閱讀正則表達(dá)式時(shí)候,先拆分分支,再對(duì)每個(gè)分支分析,每個(gè)分支從左至右,劃分成一個(gè)個(gè)的子表達(dá)式,根據(jù)元字符+量詞理解子表達(dá)式描述的規(guī)則,子表達(dá)式中還可能有分支,再遞歸地拆分和分析不同分支即可。

正則編程

test、split、search、match、replace\

驗(yàn)證

// test返回是否成功匹配到子串
/\d+/g.test('1234'); // true

檢索

// search返回匹配到的子串的index
'1234abc5678'.search(/\d+/g) // 1

提取

match方法匹配到則返回匹配結(jié)果,否則返回null。

在全局模式下match返回所有匹配到的子串?dāng)?shù)組。

在非全局模式下,match返回一個(gè)數(shù)組,第一個(gè)元素是匹配到的子串,后面元素是分組。返回的結(jié)果中還包括輸入字符串、匹配到的子串的index等信息。

在全局檢索模式下,match() 即不提供與子表達(dá)式匹配的文本的信息,也不聲明每個(gè)匹配子串的位置。如果您需要這些全局檢索的信息,可以使用 RegExp.exec()。

'1a2 3b4'.match(/(\d+)(\w+)(\d+)/g) // ["1a2", "3b4"]
'1a2 3b4'.match(/(\d+)(\w+)(\d+)/) // ["1a2", "1", "a", "2", index: 0, input: "1a2 3b4", groups: undefined]

替換

"2017-06-12".replace(/(\d{4})-(\d{2})-(\d{2})/, function ($0, $1, $2, $3) {
    return `${$2}/${$1}/${$3}`;
})
// "06/2017/12"

總結(jié)

正則表達(dá)式中的主要組成元素包括 字符字面量、字符組、量詞、錨字符、分組、選擇分支、反向引用。

這里需要注意一些符合,它們?cè)诓煌瑘?chǎng)景有不同含義

括號(hào)的作用

  • 分組
  • 非捕獲
  • 位置

問(wèn)號(hào)的作用

  • 惰性
  • 量詞
  • 位置
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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