「正則表示式」全解
theme: qklhk-chocolate
我正在參加「掘金·啟航計劃」
前言
談起正則表示式每次都是去看看文件,看了就忘,大部分實際用的時候都是谷歌一下答案,那麼為了提升對正則表示式的理解,就讓我們一起通過案例再次學習吧!!!
註明:以下知識內容是對 現代 JavaScript 教程-正則篇的筆記 ,一切請以官方為準,這裡只是做了簡單的筆記。
強烈推薦!!!
除此之外,額外加了一些小知識。
正則測試網站: 連結
正則軌道圖網站:連結
修飾符
g
全域性
i
不區分大小寫
m
多行模式
s
啟用 “dotall” 模式,允許點 .
匹配換行符 \n
u
開啟完整的 Unicode 支援,該修飾符能夠正確處理代理對
y
粘滯模式,在文字中的確切位置搜尋
```js
let str = "we we W WE we dWEd dWE dwed wed ddwe"
console.log( str.match(/we/gi) ) // 表示全域性搜尋 we 不區分大小寫
// /we/i/ 表示匹配第一個 we, 沒有匹配項,不會收到一個空陣列,而是會收到 null
// 如果希望保底為陣列
console.log( str.match(/we/i ) || [] )
// 替換 console.log( "we we W WE we dWEd dWE dwed wed ddwe".replace(/we/i, "hone") )
// 找到至少一個匹配項則返回 true,否則返回 false
/we/i.test(str) // true
```
字元類
| 正則 | 含義 |
| --- | --- |
| /\d/
| 數字:0-9 |
|/\s/
| 空格符號:包括空格,製表符 \t
,換行符 \n
和其他少數稀有字元,例如 \v
、\f
和 \r
|
|/\w/
| 「單字」字元:拉丁字母或數字或下劃線 _
。非拉丁字母(如西裡爾字母或印地文)不屬於 \w
|
let str = "+7(903)-123-45-67"
console.log( str.match(/\d/) ) // 7 匹配第一個
console.log( str.match(/\d/g) ).join('') // 查詢所有,最終結果 79031234567
str.replace(/\D/g, "") // 查詢非數字並刪除,最終結果 79031234567
反向類
| 正則 | 含義 |
| --- | --- |
| /\D/
| 除 /d
以外的任何字元 |
|/\S/
| 除 /s
以外的任何字元 |
|/\W/
| 除 /w
以外的任何字元 |
點(.)匹配「任何字元」
點 .
它與除換行符之外的任何字元匹配, 預設情況下,點與換行符 \n
不匹配
寫成 /h./s, 就可以匹配任何字元
對於空格,平時容易忽略
js
// 以下兩種都可以匹配到
console.log("1 - 2".match(/\d - \d/))
console.log("1 - 2".match(/\d/s-\s\d/))
⚠️注意:在正則表示式中,所有字元都很重要。
Unicode:修飾符 "u" 和類 \p{...}
Unicode 屬性 \p{…}
現代JS教程詳細連結
js
// \p{L} 表示任何語言中的一個字母
let str = "A ბ ㄱ"; alert( str.match(/\p{L}/gu) ) // A,ბ,ㄱ
console.log( str.match(/\p{L}/g) ); // null(沒有匹配項,因為沒有修飾符 "u")
修飾符 u
表示啟用正則表示式中對 Unicode 的支援。
- 4 個位元組長的字元被以正確的方式處理:被看成單個字元,而不是 2 個 2 位元組長的字元。
- Unicode 屬性可以被用於查詢:
\p{…}
。
錨點:字串開始 ^ 和末尾 $
插入符號 ^
匹配文字開頭,而美元符號 $
則匹配文字末尾
js
// /^hone/ 的意思是字串開始,緊接著就是 ^hone
/^hone/.test("hone age 18")
// /18$/ 是否以18結尾
/18$/.test("hone age 18")
測試完全匹配
js
// 所對應的匹配項必須正好在文字 '^' 的開頭之後開始,並且結尾 '$' 必須緊跟其後
"12:24".test(/^\d\d:\d\d$/) // 可以匹配到
錨點 ^
和 $
屬於測試,它們的寬度為零。
什麼字串可以匹配模式 ^$
? 答:空字串是唯一的匹配項:它開始並立即結束
錨點 ^ $ 的多行模式,修飾符 "m"
m
啟用多行模式修飾符 ,可以匹配每一行的^
和$
預設情況下,錨點
^
僅匹配文字的開頭,在多行模式下,它匹配行的開頭
搜尋行的末尾 $
\d$
尋找每行的最後一個數字。
查詢每行以數字開頭,中間匹配重複出現1次或者更多次數字,並且以數字結尾的匹配字元
尋找新的一行
可以使用 \d\n 來尋找新的一行
詞邊界:\b
遇到 \b
時,它會檢查字串中的位置是否是詞邊界
⚠️注意: 詞邊界
\b
不適用於非拉丁字母
轉義,特殊字元
轉義
我們想要找到一個 +
號,,要將特殊字元用作常規字元,那麼就需要在前面加上:\+
。
- 要在字面意義上搜索特殊字元
[ \ ^ $ . | ? * + ( )
,我們需要在它們前面加上一個反斜槓 \
(“轉義它們”)。
集合和範圍 [...]
集合
[…]
表示查詢滿足的 "任意一個" 。
範圍
方括號也可以包含 字元範圍
字元類是某些字元集合的簡寫:
- \d —— 和 [0-9]
相同
- \w —— 和 [a-zA-Z0-9_]
相同
- \s —— 和 [\t\n\v\f\r ]
外加少量罕見的 Unicode 空格字元相同
排除範圍
[^…]
"排除"範圍匹配
[^aeyo]
—— 匹配除了'a'
、'e'
、'y'
或'o'
之外的任何字元。[^0-9]
—— 匹配除了數字之外的任何字元,與\D
作用相同。[^\s]
—— 匹配任何非空格字元,與\S
作用相同[^]
—— 表示任意字元,即對空值取反,得到全集
[…] 中的轉義
在方括號中,我們可以使用絕大多數特殊字元而無需轉義:
- 符號 . + ( )
無需轉義。
- 在開頭或結尾(未定義範圍)的連字元 -
不會被轉義。
- 插入符號 ^
僅在開頭會被轉義(表示排除)。
- 右方括號 ]
總是會被轉義(如果我們需要尋找那個符號)
如果你為了“以防萬一”轉義了它們,這也不會有任何問題
範圍和修飾符 “u”
如果集合中有代理對(surrogate pairs),則需要標誌 u
才能使它們正常工作。
```js
console.log( '𝒳'.match(/[𝒳𝒴]/u) ) // 𝒳
// 忘記寫 u '𝒳'.match(/[𝒳-𝒴]/); // Error: Invalid regular expression ```
量詞 +, *, ? 和 {n}
{n}
在一個字元(或一個字元類,或 [...]
等)後附加一個量詞,用來指出我們具體需要的數量。
\d{5}
表示 5 位數,與 \d\d\d\d\d
相同。
確切的位數:{5}
範圍:{2,5}
,匹配 2-5 個
\d{2,}
查詢大於2的數字
縮寫
+
代表 一個或多個,與 {1,}
相同。
?
代表 零個或一個,與 {0,1}
相同。換句話說,它使得符號變得可選。
*
代表 零個及以上,與 {0,}
相同。也就是說,字元可以出現任何次數或者不出現。
貪婪量詞和惰性量詞
捕獲組 (...)
- 方法
str.match
僅當不帶修飾符g
時返回捕獲組。 - 方法
str.matchAll
始終返回捕獲組
```js // 域名搜尋 "site.com my.site.com".match(/(\w+.)+\w+/g) // site.com my.site.com
// 電子郵件 "[email protected] @ [email protected]".match(/[-.\w][email protected]([\w-]+.)+[\w-]+/g) // [email protected], [email protected] ```
巢狀組
js
let str = '<span class="my">'
let regexp = /<(([a-z]+)\s*([^>]*))>/
let result = str.match(regexp)
alert(result[0]) // <span class="my">
alert(result[1]) // span class="my"
alert(result[2]) // span
alert(result[3]) // class="my"
可選組
js
'a'.match(/a(z)?(c)?/)
命名組
```js
// 這裡的
// 匹配的組在 .groups 屬性中 let groups = str.match(dateRegexp).groups;
alert(groups.year); // 2019 alert(groups.month); // 04 alert(groups.day); // 30 ```
替換中的捕獲組
$n
中的 n
是組號
```js
let str = "John Bull";
let regexp = /(\w+) (\w+)/; // 這裡分成兩組
// $1 表示 John,$2 表示 Bull alert( str.replace(regexp, '$2, $1') ); // Bull, John
// 命名的括號 引用為 $
alert( str.replace(regexp, '$
非捕獲組
通過在組的開頭新增 ?:
來排除編號。
```js
let str = "Gogogo John!";
// ?: 從捕獲組中排除 'go' let regexp = /(?:go)+ (\w+)/i; let result = str.match(regexp); alert( result[0] ); // Gogogo John(完整的匹配項) alert( result[1] ); // John alert( result.length ); // 2(在陣列中沒有其他陣列項) ```
模式中的反向引用:\N
和 \k<name>
按編號反向引用:\N
``js
let str =
He said: "She's the one!".`;
let regexp = /(['"])(.*?)\1/g;
alert( str.match(regexp) ); // "She's the one!"
``
程式碼執行過程:正則表示式引擎會找到第一個引號
(['"])` 並記住其內容。那是第一個捕獲組。
在模式中 \1
表示“找到與第一組相同的文字”,在我們的示例中為完全相同的引號。
與此類似,\2
表示第二組的內容,\3
—— 第三分組,依此類推。
- 注意:如果我們在捕獲組中使用
?:
,那麼我們將無法引用它。用(?:...)
捕獲的組被排除,引擎不會記住它。 - 注意:在替換字串中我們使用美元符號:
$1
,而在模式中 —— 使用反斜槓\1
。
按命名反向引用:\k<name>
要引用命名的捕獲組,我們可以使用:\k<name>
``js
let str =
He said: "She's the one!".`;
let regexp = /(?['"])(.*?)\k
/g;
alert( str.match(regexp) ); // "She's the one!" ```
選擇 (OR)
用豎線 |
表示
gr(a|e)y
等同於gr[ae]y
gra|ey
表示gra
或ey
前瞻斷言與後瞻斷言
前瞻斷言
x(?=y)
僅在後面是 Y
時匹配 X
js
"1 turkey costs 30€".match(/\d+(?=€)/) // 30
否定的前瞻斷言
X(?!Y)
,意思是“搜尋 X
,但前提是後面沒有 Y
js
"1 turkey costs 30€".match(/\d+(?!€)/) // 1
後瞻斷言
- 肯定的後瞻斷言:
(?<=Y)X
,匹配X
,僅在前面是Y
的情況下。 - 否定的後瞻斷言:
(?<!Y)X
,匹配X
,僅在前面不是Y
的情況下。js "1 turkey costs $30".match(/(?<=\$)\d+/) // 30 "1 turkey costs $30".match(/(?<!\$)\b\d+/g)) // 1
捕獲組
js
"1 turkey costs 30€".match(/\d+(?=(€))/) // 30, €
"1 turkey costs $30".match(/(?<=(\$))\d+/) // 30, $
災難性回溯
粘性修飾符 "y",在位置處搜尋
y
修飾符讓我們能夠在源字串中的指定位置進行搜尋
js
let str = 'let varName = "value"';
let regexp = /\w+/y;
regexp.lastIndex = 3;
alert( regexp.exec(str) ); // null(位置 3 有一個空格,不是單詞)
regexp.lastIndex = 4; alert( regexp.exec(str) ); // varName(在位置 4 的單詞)
正則表示式和字串的方法
str.match(regexp)
在字串 str
中查詢 regexp
的匹配項。
- 如果
regexp
不帶有修飾符g
,則它以陣列的形式返回第一個匹配項,其中包含捕獲組和屬性index
(匹配項的位置)、input
(輸入字串,等於str
) - 如果
regexp
帶有修飾符g
,則它將返回一個包含所有匹配項的陣列,但不包含捕獲組和其它詳細資訊。 - 如果沒有匹配項,則無論是否帶有修飾符
g
,都將返回null
str.matchAll(regexp)
它主要用來搜尋所有組的所有匹配項。
與 match
相比有 3 個區別:
- 它返回一個包含匹配項的可迭代物件,而不是陣列。我們可以用
Array.from
將其轉換為一個常規陣列。 - 每個匹配項均以一個包含捕獲組的陣列形式返回(返回格式與不帶修飾符
g
的str.match
相同)。 - 如果沒有結果,則返回的是一個空的可迭代物件而不是
null
。
str.split(regexp|substr, limit)
alert('12, 34, 56'.split(/,\s*/)) // 陣列 ['12', '34', '56']
str.search(regexp)
方法 str.search(regexp)
返回第一個匹配項的位置,如果沒找到,則返回 -1
str.replace(str|regexp, str|func)
用於搜尋和替換的通用方法
js
// 用冒號替換連字元
alert('12-34-56'.replace("-", ":")) // 12:34-56
str.replaceAll(str|regexp, str|func)
這個方法與 str.replace
本質上是一樣的,但有兩個主要的區別:
- 如果第一個引數是一個字串,它會替換 所有出現的 和第一個引數相同的字串,而
replace
只會替換 第一個。 - 如果第一個引數是一個沒有修飾符
g
的正則表示式,則會報錯。帶有修飾符g
,它的工作方式與replace
相同。
replaceAll
的主要用途是替換所有出現的字串。
regexp.exec(str)
regexp.exec(str)
方法返回字串 str
中的 regexp
匹配項。與以前的方法不同,它是在正則表示式而不是在字串上呼叫的。
它的行為取決於正則表示式是否具有修飾符 g
。
如果沒有修飾符 g
,則 regexp.exec(str)
會返回與 第一個匹配項,就像 str.match(regexp)
那樣。這種行為並沒有帶來任何新的東西。
但是,如果有修飾符 g
,那麼:
- 呼叫
regexp.exec(str)
會返回第一個匹配項,並將緊隨其後的位置儲存在屬性regexp.lastIndex
中。 - 下一次這樣的呼叫會從位置
regexp.lastIndex
開始搜尋,返回下一個匹配項,並將其後的位置儲存在regexp.lastIndex
中。 - ……以此類推。
- 如果沒有匹配項,則
regexp.exec
返回null
,並將regexp.lastIndex
重置為0
。
regexp.test(str)
方法 regexp.test(str)
查詢匹配項,然後返回 true/false
表示是否存在。