瞎說系列之正則表達式入門

前言

一直以來也沒有寫文章的習慣,前不久在公司內部進行技術分享以後,發現寫技術文章仍是很是重要的,所以將以前分享過的內容整理出來,寫成此文章。以後會不按期更新瞎說系列教程,敬請期待。若有不妥,歡迎你們不吝賜教。javascript

簡介

正則表達式,又稱規則表達式。(在代碼中常簡寫爲regex)。正則表達式一般被用來檢索,替換那些符合某個模式的文本。html

正則表達式是對字符串(包括普通字符(例如:a到z之間的字母)和特殊字符(又稱爲元字符))操做的一種邏輯公式。用事先定義好的一些特定字符,以及這些特定字符的組合,組成一個「規則字符串」,這個規則字符串用來表達對字符串的一種過濾邏輯。正則表達式是一種文本模式,模式描述在搜索文本時要匹配的一個或多個字符串。java

語法

下面詳細的介紹正則表達式的語法。正則表達式

字符類

元字符是擁有特殊意義的字符。windows

經常使用元字符
字符 等價 含義
. [^\n\r] 匹配任何單個字符,除了換行符和回車符。
\w [a-zA-Z_0-9] 匹配任何單詞字符(數字,字母,下劃線)
\W [^a-zA-Z_0-9] 匹配任何非單詞字符
\d [0-9] 匹配數字。
\D [^0-9] 匹配非數字。
\s [\n\f\r\t\v\x0B] 匹配空白字符。(包括空格符,製表符,回車符,換行符,垂直換行符,換頁符)。
\n \n 匹配換行符。
\f \f 匹配換頁符。
\r \r 匹配回車符。
\t \t 匹配製表符。
\v \v 匹配垂直製表符。
\ \ 轉義字符,轉義後面字符所表明的含義(好比\*匹配的是*)
| | 表示字符匹配是或的關係(好比x|y匹配的是x或y中的一個字符)。
\0 Null 匹配null

換行符和回車符的區別能夠參考阮一峯的文章數組

量詞

量詞用來表示匹配的數量。函數

字符 等價 含義
n? n{0,1} 匹配任何包含0個或1個的字符串(最多有一個)。
n+ n{1,} 匹配任何包含至少1個的字符串(至少一個)。
n* n{0,} 匹配任何0個或多個的字符串。
n{x} n{x} 匹配包含x個n的序列的字符串。
n{x,} n{x,} 匹配包含至少x個n的序列的字符串。
n{x,y} n{x,y} 匹配出現x次可是不超過y次的n的序列的字符串。
定位符

定位符顧名思義用來肯定位置的字符。post

字符 含義
^ 單獨使用表示匹配表達式的開始。
$ 匹配表達式的結尾。
\b 匹配一個單詞字符的邊界。單詞字符後面或前面不與另外的單詞字符相鄰,能夠理解爲匹配一個單詞的開始或者結束。(好比:/\bx/能夠匹配s x)。
\B 匹配非單詞的邊界。能夠理解爲查找不處在單詞的開始或者結束的位置。
標誌字符
字符 含義
g 匹配全局。
i 不區分大小寫。
m 多行搜索。
範圍類
字符 含義
[0-9] 匹配0到9之間的任意一個數字
[a-z] 匹配a到z之間的任意一個字符。
[A-Z] 匹配A到Z之間的任意一個字符。
[^0-9] 匹配不在0到9之間的任意一個字符。
其餘規則
字符 含義
(pattern) 匹配pattern並獲取這一匹配,即捕獲組。
(?:pattern) 匹配pattern但不獲取匹配結果,即非捕獲組。
(?=pattern) 正向確定預查,在任何匹配pattern的字符串開始處匹配查找字符串。這是一個非捕獲組匹配。例如/windows(?=95)/能夠匹配windows95中的windows,可是不能匹配windows98中的windows。能夠理解爲匹配(?=pattern)外面且在前面的內容。
(?!pattern) 正向否認預查,在任何不匹配pattern的字符串開始處匹配查找字符串。這也是一個非捕獲組匹配。例如/windows(?!95)/能夠匹配windows98中的windows,可是不能匹配windows95中的windows
(?<=pattern) 反向確定預查,與正向確定預查類似,只是方向相反。正向確定預查是匹配前面的內容,而反向確定預查是匹配後面的內容。例如(?<=95)windows能匹配95windows中的windows,可是不能匹配98windows的windows,也不能匹配windows95中的windows。
(?<!pattern) 反向否認預查,與正向否認預查類似,只是方向相反。例如(?<!95)windows能匹配98windows中的windows,可是不能匹配95windows中的windows,也不能匹配windows95中的windows。

運算符優先級

正則表達式是從左到右進行計算,並遵循優先級順序。相同的優先級從左到右計算,不一樣優先級從高到低計算。下表爲表達式運算符從高到低的優先級順序。網站

字符 含義
\ 轉義字符。
(), (?:), (?=), [] 圓括號和方括號。
*, +, ?, {n}, {n,}, {n,m} 量詞等限定符。
^, $, \任何元字符、任何字符 定位點和序列(即位置和順序)。
| 」或「操做。

貪婪模式和非貪婪模式

正則表達式的貪婪模式和非貪婪模式是相對於量詞這個限定符來講的。默認狀況下,正則的全部量詞(限定符)都是貪婪模式,即儘量多的去匹配字符。而在量詞(限定符)後面加上?就變成了非貪婪模式,即儘量少的去匹配字符。ui

舉個🌰

正則表達式/a{2,5}/,能夠匹配aa,aaa,aaaa,aaaaa。這是貪婪模式,即儘量多的去匹配。

正則表達式/a{2,5}?/,只會匹配aa。這是非貪婪模式,即儘量少的去匹配。

疑問?

若是正則表達式是<div>.+?</div>cc的話,匹配結果倒是<div>test1</div><div>test2</div>cc。不少人在平常開發中會對這種狀況感到困惑。這裏明明是非貪婪模式,爲何匹配結果不是<div>test1</div>cc呢?這是由於不管是貪婪模式仍是非貪婪模式都有一個前提條件:整個表達式必須匹配成功。當表達式匹配到<div>test1</div>時,後面的cc沒法匹配成功,只有匹配到<div>test1</div><div>test2</div>時,後面的cc纔會匹配成功。

分組

正則的分組主要經過小括號來實現,括號的子表達式做爲一個分組,括號後面能夠緊跟量詞表示重複的次數。

捕獲組

捕獲性分組,一般由一對小括號加上子表達式組成。正則會把每一個分組裏面的內容保存起來,供後續調用。其中由分組捕獲的串會從1開始編號,依次類推。這種引用既能夠在表達式內部,也能夠在表達式外部。

舉個🌰
const regex = /(\d{4})-(\d{2})-(\d{2})/
const str = '2019-10-21'
console.log(RegExp.$1) // 2019
console.log(RegExp.$2) // 10
console.log(RegExp.$3) // 21
複製代碼

捕獲組引用經常使用來進行替換操做。

const regex = /(\d{4})-(\d{2})-(\d{2})/
const str = '2019-10-21'
const result = str.replace(regex, '$3/$2/$1')
// => 21/10/2019
複製代碼
嵌套分組的捕獲

在嵌套的分組中是以左括號出現的順序進行捕獲。

舉個🌰
const regex = /((I) (am) (your) (father))/
const str = 'I am your father'
RegExp.$1 // I am your father
RegExp.$2 // I
RegExp.$3 // am
RegExp.$4 // your
RegExp.$5 // father
複製代碼
反向引用

捕獲組捕獲到的內容,不只能夠在正則表達式外部經過程序進行引用,也能夠在正則表達式內部進行引用,在內部被反向引用的值繼續參與匹配,而在表達式內部進行引用的方式被稱爲反向引用。其格式爲\數字。反向引用一般是用來查找或限定重複,限定指定標識配對出現。

反向引用匹配原理

捕獲組在匹配成功時,會將子表達式匹配到的內容,保存在一個以數字編號的組裏,這時能夠經過反向引用的方式,引用這個局部變量的值。一個捕獲組在匹配成功以前,它的內容是不肯定的,一旦匹配成功,它的內容就肯定了,反向引用的內容也就是肯定的了。

舉個🌰
const regex = /(\w{3}) is \1/
regex.test('skr is skr') // true
regex.test('krs is krs') // true
regex.test('krs is skr') // false
複製代碼

在表達式匹配成功以後,\1引用了第一個被分組捕獲的內容即skr或者krs,因此前兩個會匹配成功。

可是,若是編號越界了,則會被當成普通的表達式:

const regex = /(\w{3}) is \\6/
regex.test('skr is skr') // false
regex.test('skr is \6') // true
複製代碼
非捕獲組

有時咱們只想要括號的原始功能,只進行分組,而不進行捕獲,即既不在表達式外部引用,也不在表達式內部反向引用。此時咱們可使用非捕獲分組。語法爲(?:p)。

舉個🌰
const regex = /(?:\d{4})-(\d{2})-(\d{2})/
const date = '2019-10-21'
RegExp.$1 // 10
RegExp.$2 // 21
複製代碼

在這個例子中使用了非捕獲分組,所以(?:\d{4})不會捕獲任何字符串,因此$1爲(\d{2})捕獲的內容。

RegExp對象

RegExp構造函數會建立一個正則表達式對象,用於將文本與一個模式匹配。

有兩種方法來建立一個RegExp對象:一個是字面量,另外一個是RegExp構造函數。要指示字符串,字面量的參數不使用引號,而構造函數的參數使用引號。

當表達式被賦值時,字面量形式提供了正則表達式的編譯狀態。當你在循環中使用字面量構造一個正則表達式時,表達式不會在每一次迭代中被從新編譯。而經過構造函數建立的正則表達式提供了運行時編譯。若是你知道表達式模式將會改變,或者你事先不知道什麼模式,而是從另外一個來源獲取的,好比用戶的輸入,那麼這些狀況均可以使用構造函數模式。

當使用構造函數建立表達式對象時,須要常規的字符轉義(即在前面加上反斜槓\)。下面兩種狀況是等價的:

const regexp = new RegExp('\\w+')
const regexp = /\w+/
複製代碼
RegExp對象屬性
屬性 含義
global RegExp對象是否具備標誌g。
ignoreCase RegExp對象是否具備標誌i。
lastIndex 一個整數,表示下一次匹配的開始位置。
multline RegExp對象是否具備標誌
source 正則表達式的源文本。
RegExp對象方法
exec()

RegExp.prototype.exec():該方法用於檢索字符串中正則表達式的匹配。

返回值是一個數組,其中存放匹配的結果。若是未找到匹配,將返回null。此數組的第0個元素是與正則表達式相匹配的文本。第1個元素是與RegExpObject的第1個子表達式匹配的文本,第2個元素是與RegExpObject的第2個子表達式匹配的文本,以此類推。除了數組元素和length屬性外,該方法還返回兩個屬性。index屬性表示的是匹配的文本第一個字符的位置。input屬性表示的是被檢索的字符串。在調用非全局的RegExp對象的exec()方法時,返回的數組與調用String.match()返回的數組是相同的。

可是當RegExpObject時一個全局正則表達式時,它會在RegExpObject的lastIndex屬性指定的字符處開始檢索字符串。當exec()方法找到了相匹配的文本後,它將把RegExpObject的lastIndex屬性設置爲匹配文本的最後一個字符串的下一個位置,所以能夠反覆調用exec()來遍歷字符串中全部匹配的文本,當exec()再也找不到匹配的文本時,它將返回null,而且lastIndex屬性也會重置爲0。

注意:若是在一個字符串中完成了一次匹配以後,想要檢索新的字符串,必須手動把lastIndex屬性設置爲0。

舉個🌰
var str = "2019.10.21"
var regexp = /\b(\d+)\b/g
console.log( regexp.exec(str) )
console.log( regexp.lastIndex)
console.log( regexp.exec(str) )
console.log( regexp.lastIndex)
console.log( regexp.exec(str) )
console.log( regexp.lastIndex)
console.log( regexp.exec(str) )
console.log( regexp.lastIndex)
// => ["2019", "2019", index: 0, input: "2019.10.21"]
// => 4
// => ["10", "10", index: 5, input: "2019.10.21"]
// => 7
// => ["21", "21", index: 8, input: "2019.10.21"]
// => 10
// => null
// => 0
複製代碼
test()

RegExp.prototype.test()方法執行一個檢索,用來查看正則表達式與指定的字符串是否匹配。若是正則表達式與指定的字符串匹配,則返回True,不然返回false。

舉個🌰
const regexp = /\d+/
const str = 'abc123'
regexp.test(str) // true
複製代碼

其餘相關API

search()

search()方法用於檢索字符串中指定的子字符串,或檢索與正則表達式相匹配的子字符串。返回第一個與regexp相匹配的子串的起始位置。

注意:search()放不執行全局匹配,它將忽略標誌g。

舉個🌰
const regexp = /\d/
const str = 'abc123'
str.search(regexp) // 3
複製代碼
match()

match()放可在字符串內檢索指定的值,或找到一個或多個正則表達式的匹配。返回值是一個數組,存放匹配結果,該數組的內容依賴於正則表達式是否有全局標誌g。

若是regexp沒有標誌g,那麼match()就在string中只執行一次匹配。若是沒有匹配就返回null。若是有匹配,它將返回一個數組,該數組的第0個元素存放的是匹配的文本,其他元素存放的是與正則表達式的子表達式(即捕獲組)相匹配的文本。此外還包含兩個對象屬性,index屬性聲明的是匹配文本的起始字符在字符串中的位置,input屬性聲明的是對該字符串的引用。

若是regexp具備標誌g,match()將執行全局檢索,找到字符串中全部匹配的子字符串。若是沒有找到就返回null。若是找到,就返回一個數組。數組中存放的元素是字符串中全部匹配到的子串,沒有index和input屬性。

舉個🌰
const regexp = /(\d{4})-(\d{2})-(\d{2})/
const str = '2019-10-21'
str.match(regexp) // ['2019-10-21','2019','10','21',index: 0,input: '2019-10-21']
複製代碼
const regexp = /(\d{4})-(\d{2})-(\d{2})/g
const str = '2019-10-21'
str.match(regexp) // ['2019-10-21']
複製代碼
replace()

replace()方法用於在字符串中用一些字符替換另外一些字符,或替換一個與正則表達式匹配的子串。返回值是一個被替換後的字符串。若是regexp具備標誌g,則會替換全部相匹配的文本,若是沒有標誌g,則只替換第一個匹配到的文本。替換的內容能夠是字符串,也能夠是函數。若是是字符串,那麼替換文本中的$具備特殊的意義。

字符 替換文本
$1,$2……,$99 與 regexp 中的第 1 到第 99 個子表達式相匹配的文本。
$& 與 regexp 相匹配的子串。
$` 位於匹配子串左側的文本。
$' 位於匹配子串右側的文本。
$$ 直接量符號。
舉個🌰
const regexp = /\w/g
const str = 'acdfe'
str.replace(regexp, 'b') // bbbbb
複製代碼
const regexp = /\w/
const str = 'acdfe'
str.replace(regexp, 'b') // bcdfe
複製代碼
const regexp = /\w/g
const str = 'acdfe'
str.replace(regexp, function(){
    return 'b'
}) // bbbbb
複製代碼

總結

很感謝你們花時間把個人文章看完。正則表達式是一門」玄學「,功能很是強大,而且裏面還有不少鮮爲人知的」騷操做「,但願你們可以繼續探索它的奧祕,多多動手實際操做一下,畢竟紙上得來終覺淺,絕知此事要躬行。最後再給你們推薦一個正則表達式可視化的網站regexper

相關文章
相關標籤/搜索