一文破解正則密碼
王志遠: 微醫前端技術部。
前言
正則,熟悉的陌生人,我們在表單校驗中見到它,也在框架源碼 html 轉 ast 樹的 parser 原理中見到它;常常見到,需要時百度一搜,確實能用,卻又一碰到就發憷,原因很簡單,這火星文,誰看得懂呀!
本文目標,帶你走進正則世界,作爲一篇認真負責的科普文,一定要做到讓你們一遍學懂卻不會,於是反覆來查看。
正則歷史
正則其實就是規則的設定,用於驗證或者獲取信息。
真正起源於神經網絡
20 世紀 40 年代,兩位神經生理學家研究出了一種用數學方式描述神經網絡的方法,並以此在 1956 年發表了一篇論文《正則表達式搜索算法》,主要描述了一種叫做 正則集合(Regular Sets) 的符合。
在計算機世界大放異彩
UNIX 之父在十年後的 1968 年,發表文章《正則表達式搜索算法》,並將正則移植到了大名鼎鼎的文本搜索工具grep
中。
正則爲什麼存在
理解一個東西,不要一上來就背應用層的 API,要去理解,人的大腦本來就是被設計成對理解的東西很容易記憶,不要浪費自己的天賦。
我們說過,正則其實就是規則的設定,用於驗證或者獲取信息。匹配動作最粗暴的無疑是一一對應,a 對 a,b 對 b,這個規則就是完全一樣才匹配,但這無疑太低效;一個事物很容易有共性,共性構成集合,比如手機號 13 位,郵箱帶 @等等,正則就是讓我們更高效的匹配或獲取這些特定規則集合的存在。
正則如何做到
正則中的處理方式就是設定子規則,讓某些符號不再代表本身,而是代表一些子規則,就像搭建大樓,我們希望找到自己想要的規則,就得用合適的最小磚塊,再設定使用量,就搭建完成了。
這個子規則,就是元字符,如我們常見的\d
,代表單個 0-1 數字,\s
就是換行指標等空白字符;需要注意的是,我們剛剛一直在強調單個,這其實也很好理解,對於文本而言,最小單元自然是單個字符,有了最小單元,我們再加上重複規模,就可以搭建我們自己的大樓了。
至此,我們對正則世界基本的瞭解算是到位了,開始更接地氣的分享叭!
磚塊:元字符
那我們先來了解元字符,有四個維度,分別是【字符組】、【取反字符組】、【常用字符組】、【空白字符】,可以記憶爲 【3 + 1】,依次理解
字符組
基礎使用
對於單字符選擇而言,在正則中的術語被稱爲字符組,接下來我們都會用這個術語,但不要被迷惑,它並不是匹配一組數據,而只是匹配一組數據中的一個字符,這點很關鍵。
語法: [xxx]
匹配規則: 目標文本需包含【任意一個包含在括號中的元素】
獲取信息規則: 將獲取第一個【任意一個包含在括號中的元素】
舉例: /[abc]/
這個正則將匹配 a、b、c 中的任何一位且只有一位,默認匹配第一位,使用測試平臺會得到如下結果
取反字符組
取反邏輯
正常使用是範圍內選取,不過也會出現【除了這些之外】的範圍選取,這時就需要用到取反了
語法: [^xxx]
匹配規則: 目標文本需包含【任意一個不包含在括號中的元素】
獲取信息規則: 將獲取第一個【任意一個不包含在括號中的元素】
舉例: /[^abc]/
這個正則將匹配非 a、b、c 中的任何一位且只有一位,默認匹配第一位,使用測試平臺會得到如下結果
常用字符組
八二原則同樣適用在字符組中,有很多常見的匹配規則,沒必要重複寫,於是正則就把這些特殊字符組分別取了個別名
主要分爲如下【3+1】概念,三對 + 一個
其他常用字符組
空白字符
這類字符比較特殊,單獨拎出來,方便後期直接使用
到此,我們也就知道了如何去匹配單個字符,這就是我們的 “最小規模”,但一個個匹配肯定是不行,那正則將冗長到難以忍受,所以我們還需要重複,這就是量詞的作用。
要多少塊磚:量詞
理解了字符組,我們就要了解規模了,這在正則中有個術語 --- 量詞,還是【3+1】,依次理解
這個沒有很複雜的東西,唯一要注意的是,正則默認是隻匹配一次的,即一次匹配完後就算後文還有符合的內容也不再獲取,這涉及到修飾符g
,後面再補充;
看案例就行,我們還是以[abc]
作爲字符組最小單元來演示。
* 號:0-n 次
正則: /[abc]*/
匹配規則: 目標文本無需包含【任意一個包含在括號中的元素】,此匹配規則一定成立
獲取信息規則: 將獲取第一段【包含在括號中且連續的元素組】
+ 號:1+n 次
正則: /[abc]+/
獲取信息規則: 將獲取第一段【包含在括號中且連續的元素組】
匹配規則: 目標文本需包含【至少一個包含在括號中的元素】
? 號:0 次或 1 次
正則: /[abc]?/
**匹配規則:**此匹配規則一定成立
**獲取信息規則:**將獲取第一個【任意一個包含在括號中的元素】
{} 符號:精確控制次數
上面其實都屬於特殊案例,我們可以通過{}
精確控制匹配次數,主要有三個用法
-
{m}:必須出現 m 次
-
{m, n}:可以出現 m-n 次
-
{m,}:至少出現 m 次
我們對這三種情況進行演示,動圖如下
量詞模式
量詞還涉及到模式問題,因爲量詞有範圍,這就意味着可取多可取少,但計算機是不允許有歧義的,所以量詞存在三種模式
-
貪婪模式:默認,會盡可能匹配多的內容
-
懶惰模式:量詞後面加個
?
,會盡可能少匹配內容 -
獨佔模式:量詞後面加個
+
,不觸發回溯動作
舉例見模式區別
測試用例: aaabb
測試正則:
-
貪婪模式:
/a*/
-
懶惰模式:
/a*?/
貪婪模式:/a*/
匹配過程:
匹配結果:
對應輸出結果: ['aaa','','','']
懶惰模式:/a*?/
匹配過程:
匹配結果:
對應輸出結果: ['','a','','a','','a','','','']
補充案例
至此,我們就完成了對量詞規則的學習
正則模式
既然量詞有模式,正則本身自然也有模式,針對【大小寫、多行、點通配、備註】情況,存在【3+1】種模式
-
不區分大小寫模式
-
點通配模式
-
多行匹配模式
-
註釋模式
我們來逐一瞭解
不區分大小寫模式 (Case-Insensitive)
語法: /(?i)reg/
對應 js 爲 /reg/i
注意點:
-
不區分大小寫模式的指定方式,使用模式修飾符 (?i);
-
修飾符如果在括號內,作用範圍是這個括號內的正則,而不是整個正則;
作用: 忽略大小寫進行匹配
正則: /(?i)(cat) \1/
對應 js 爲 /(cat) \1/i
如果這時候我們希望重複單詞間保持大小寫完全一致,可以使用如下正則
正則: /((?i)cat) \1/
對應 js 爲 暫無
點通配模式(單行匹配模式 -- Single Line)
語法: /(?s)reg/
對應 js 爲暫無
注意點:
作用: 使得.
元字符可以匹配包括換行在內的所有字符
多行匹配模式
語法: /(?m)reg/
對應 js 爲/reg/m
注意點:
作用: 使得^
和$
可以匹配上每行的開頭或結尾
使用前正則: /^the|cat$/
使用後正則: /(?m)^the|cat$/
對應 js 爲/^the|cat$/m
註釋模式
語法: /(?#)reg/
對應 js 爲 暫無
注意點:
作用: 使得正則支持添加備註信息
使用正則案例: /(\w+)(?#word) \1(?#word repeat again)/
正則位置信息
對於匹配而言,就像我們看一個人是不是自己要找的人,不只有對着畫像、照片一直看這一個方法,也可以描述 TA 在什麼東西的旁邊、TA 面前是什麼、背後是什麼等等,這些位置信息在正則中同樣有需求,並且有個專門的術語 -- 斷言。
斷言,即斷定匹配文本的位置關係;前後的內容是什麼、中止的位置在哪之類,落實下來分爲三類:單詞邊界、行的開始 / 結束、環視。
行的開始 / 結束
這個我們或多或少都接觸過,如果我們要求匹配的內容出現在一行文本的開頭或結尾,就可以使用^
和$
進行位置界定。
結合之前說的【模式】中多行模式的概念,默認處理文本會被正則當成一行進行處理,無論其是否換行,這是的開始結束就等同於文首和文末;而如果想處理多行情況,只需要改變模式爲多行匹配即可,js 中語法爲/reg/m
。
單詞邊界(Word Boundary)
多行模式 +^$
可以在行的維度處理邊界問題,但如果是單詞,就無能爲力了,如我們希望在下面文本中替換tom
這個人名爲jerry
tom asked me if I would go fishing with him tomorrow.
這時如果替換的正則是/tom/
,就會出現這種錯誤的替換現象
很明顯,我們要的就是tom
,而並不是只要包含 tom 就可以的部分,這時我們就可以使用到單詞邊界的概念,設定開始截止,避免出現匹配歧義。
基礎概念
語法: \b
作用: 匹配到\w
即【[A-Za-z0-9_]】表示範圍之外的字符就中止匹配,可以理解爲邊界(Boundary)
實例
環視
我們剛剛說了邊界,包括單詞和行的邊界,其實邊界也就是要求匹配文本的前後一定是特定的內容,只是這個特定內容對行來說是^$
,對單詞邊界來說是$
;
那我們把這個特定範圍再靈活點,對於一段內容而言,有前後兩個方向、滿足或者不滿足兩個情況,意味着有四種情況,如下表。
總結下來其實就是:有尖括號則爲左,等號肯定感嘆否
正則邏輯信息
根據前文,我們已經學習了【字符組】和【量詞】的概念,就像一門編程語言,有組成物料還不夠,自然還需要一些邏輯判斷,在正則中也存在【邏輯元字符】這一概念。
邏輯元字符
| 號:或邏輯
某個資源可能以 http:// 開頭,或者 https:// 開頭,也可能以 ftp:// 開頭,那麼資源的 協議部分,我們可以使用 (https?|ftp):// 來表示。
正則優先級提升之分組
在正則中存在分組的概念,主要有兩點作用:整體和複用。
整體
代表避免語義分析有歧義,如【匹配 15 位數字或 18 位數字】,這時如果寫出這樣的正則/\d{15}\d{3}?/
;後面的\d{3}?
將代表懶惰模式匹配,這個正則會只匹配 18 位數字而非 15 位
測試正則: /\d{15}\d{3}?/
這裏就存在確定\d{3}
是一個整體的需求,這可以使用分組實現
測試正則: /\d{15}(\d{3})?/
複用
有些時候,我們也會需要用到之前匹配到的結果,如【查看文本中的連續重複單詞】,解決思路就變成了
-
寫出匹配單個單詞的正則
-
使用之前的結果進行再次匹配
第二點,就是通過分組實現的;先了解下基礎概念
基礎概念
語法: 定義使用()
,正則中訪問使用\編號
,方法中訪問使用$編號
作用: 用於分組,被括號括起來的部分默認將被保存爲子組,正則中可以通過子組編號訪問,子組編號從一遞增,也可以用語法(?:)
從而不保存子組,避免佔用編號。
分組引用語法詳解
分組引用
假定分組編號爲number
,則可以使用\number
進行引用
多編號情況
左括號是第幾個,那就是第幾個分組
不保存子組
使用此語法後不會爲這個子組分配編號
替換功能
命名分組
V8 目前已經完全實現了命名捕獲分組的提案 https://tc39.github.io/proposal-regexp-named-groups/,一起來了解下吧!
基礎概念
語法: 定義使用(?<name>)
,正則中訪問使用\k<name>
,方法中訪問使用$<name>
作用: 用於命名分組,不再使用編號訪問而是直接通過分組變量名訪問,更加準確
API 結合解構賦值
在 js 關於正則的方法中,如果存在命名分組,會存在groups
屬性,裏面存放着每個命名分組的名稱以及它們匹配到的值;結合解構賦值,會有很神奇的功效;
在 exec() 和 match() 中的使用:
exec() 和 match() 方法返回的匹配結果數組上多了一個 groups 屬性,裏面存放着每個命名分組的名稱以及它們匹配到的值
const {day, month, year} = "04-25-2017".match(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/).groups
在 replace(/.../, replacement) 中的使用:
當replacement
爲函數時,在實參列表的最末尾,多傳了一個 groups 對象
"04-25-2017".replace(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, (...args) => {
const groups = args.slice(-1)[0]
const {day, month, year} = groups
return `${day}-${month}-${year}`
})
正則編程
這是最最關鍵的部分,學來就得用上呀,我們來分享在正則在前端編程中的應用。
正則最終還是要落實到編程語言中來,讓我們來看下正則編程吧!
正則的處理可以區分爲如下四類:
-
校驗文本內容
-
提取文本內容
-
替換文本內容
-
切割文本內容
讓我們逐一瞭解
校驗文本內容
**需注意:**關於 lastIndex,即正則會將下一次匹配開始的位置 ;字符串的四個方法,每次匹配都是從 0 開始的,即 lastIndex 不變;而正則的兩個方法 exec 和 test ,如果是全局匹配,則每次匹配完都會改變 lastIndex 的值,這就會導致可能出現【處理兩次,第一次成功,第二次失敗】的情況。
var regex = new RegExp(/^\d{4}-\d{2}-\d{2}/, 'g')
regex.test('2021-12-21') // true
console.log(regex.lastIndex ) // 10
regex.test('2021-12-21') // false
console.log(regex.lastIndex ) // 0
由於我們這裏是文本校驗,並不需要找出所有的。所以建議 JavaScript 中文本校驗在使用 RegExp 時不要設置 g 模式。
字符串方法:search
search 會將字符串轉爲正則
正則方法:test
提取文本內容
字符串方法:match
match 會將字符串轉爲正則
注意: match 方法的返回值與修飾符 g 有關(沒有匹配上時返回 null)
-
沒有 g :返回標準匹配格式,即:數組的第一個元素是整體匹配的內容,接下來是分組捕獲的內容,然後是整體匹配的第一個下標,最後是目標字符串
-
有 g :返回的是一個包含所有匹配內容的數組
正則方法:exec
exec 比 match 更強大,可以解決 有修飾符 g 時 match 沒有索引信息的問題,在使用 exec 時,正則會將下一次匹配開始的位置存放在正則的屬性 lastIndex 上
替換文本內容
字符串方法:replace
切割文本內容
字符串方法:split
-
可以有第二個參數,表示結果數組的最大長度
-
如果正則使用分組時,結果數組中是包含分隔符的
前端相關 API 總結
-
string
-
match
-
split
-
search
-
replace
-
RegExp
-
test
-
exec
總結
做下總結吧,繪製知識圖譜,方便自己記憶,也方便和人分享
3 + 1 元字符;3 + 1 常用元字符;3 + 1 正則量詞;3 量詞匹配模式;3 + 1 正則匹配模式;3 + 1 正則邏輯
首先物料元字符,有四個維度,分別是【字符組】、【取反字符組】、【常用字符組】、【空白字符】,可以記憶爲 【3 + 1】;
理解了字符組,我們就要了解規模了,這在正則中有個術語 --- 量詞還是【3+1】;
量詞還涉及到模式問題,因爲量詞有範圍,這就意味着可取多可取少,但計算機是不允許有歧義的,所以量詞存在三種模式;
既然量詞有模式,正則本身自然也有模式,針對【大小寫、多行、點通配、備註】情況,存在【3+1】種模式;
就像定位,不止需要本身的絕對信息,還需要看他的相對位置信息,這個信息在正則中叫斷言,存在三種情況,【行首尾、單詞邊界和環視】,其中環視又存在前後是不是四種情況
就像編程語言,我們有了零碎的物料是不夠的,還需要邏輯,在正則中存在分支語句|
和優先級分組,分組又有三類,默認分組、非捕獲分組和命名分組
至此,我們也就用非常精煉的總結性語句概括了正則的整體脈絡啦!
尾聲
少年們,心法已定,拿走不謝,希望我能做到讓你們一遍看懂而記不住,要首尾呼應,嘗試動手自己實現下吧,有些需求會發現如果用正則的角度,會有很多很神奇的實現方式呀,而且如果能幫助到別人,也超有成就感的。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/T0JFH618B_BVTzTr8m31bw