字串的處理基本都會用正規表示式,用它來做字串的匹配、提取、替換等很方便。
但是正規表示式的學習還是有些難度的,比如貪婪匹配、非貪婪匹配、捕獲子組、非捕獲子組等概念,不止初學者難理解,有很多工作幾年的人都不理解。
那正規表示式怎麼學比較好?怎麼快速掌握正規表示式呢?
推薦一個我覺得很不錯的學習正則的方式:通過 AST 來學習。
正規表示式的匹配原理是把模式串 parse 成 AST,然後通過這個 AST 去匹配目標字串。
模式串中的各種資訊在 parse 之後都會儲存在 AST 裡面。AST 是 abstract syntax tree,抽象語法樹的意思,顧名思義,是按照語法結構組織的一棵樹,那麼從 AST 的結構上自然可以輕易的知道正規表示式支援的語法。
怎麼檢視正規表示式的 AST 呢?
可以通過 astexplorer.net 這個網站來視覺化的檢視:
切換 parse 的語言為 RegExp,就可以做正規表示式的 AST 的視覺化。
就像前面所說,AST 是按照語法來組織的一棵樹,那麼從它的結構上自然能容易地理清各種語法。
那麼我們就從 AST 的角度來學習下各種語法吧:
先從簡單的開始,/abc/ 這樣一個正則就可以匹配 'abc' 的字串,它的 AST 是這樣的:
3 個 Char,值分別是 a、b、c,型別是 simple。那之後的匹配就是遍歷 AST,分別匹配這三個字元了。
我們用 exec 的 api 測試了下:
第 0 個元素是匹配的字串,index 是匹配字串的開始下標。input 是輸入的字串。
再來試下特殊的字元:
/\d\d\d/ 是匹配三個數位的意思,\d 是正則支援的有特殊含義的元字元(meta char)。
通過 AST 我們也可以看出來,它們雖然也是 Char,但型別確是 meta:
可以通過 \d 的元字元來匹配任意數位:
哪些是 meta char 哪些是 simple char,通過 AST 來看一目瞭然。
正則支援通過 [] 的方式來指定一組字元,也就是說匹配其中任意一種字元都行。
通過 AST 我們也可以看出來,它被包裹了一層 CharacterClass,就是字元類的意思,也就是匹配它包含的任意一種字元都行。
測試下也確實是這樣:
正規表示式支援指定某個字元重複多少次,用 {from,to} 的形式,
比如 /b{1,3}/ 表示字元 b 重複 1 到 3 次,/[abc]{1,3}/ 表示這個 a/b/c 字元類重複 1 到 3 次。
通過 AST 可以看出來,這種語法叫做 Repetition(重複):
他有個 quantifier 的屬性表示量詞,這裡的型別是 range,從 1 到 3。
正則也支援一些量詞的簡寫,比如 + 表示 1 到無數次、* 表示 0 到無數次、? 表示 0 或 1 次。
分別是不同型別的量詞:
有同學可能會問,這裡的 greedy 屬性是啥意思呢?
greedy 是貪婪的意思,這個屬性就表示這個 Repetition 是貪婪匹配還是非貪婪匹配。
如果在量詞後加個 ?,你就會發現 greedy 變成 false 了,也就是切換到了非貪婪匹配:
那貪婪和非貪婪是指啥呢?
我們看個例子就知道了。
預設 Repetition 的匹配是貪婪的,只要滿足條件就一直匹配下去,所以這裡 acbac 都能匹配到。
量詞後加個 ? 就切換到了非貪婪,就只會匹配第一個了:
這就是貪婪匹配和非貪婪匹配,通過 AST 我們能夠清楚的知道貪婪和非貪婪是針對重複語法來說的,預設是貪婪匹配,在量詞後加個 ? 就可以切換到非貪婪。
正規表示式支援通過()把匹配到的一部分字串放到子組裡返回。
通過 AST 看一下:
對應的 AST 就叫做 Group。
而且你會發現它有個 capturing 的屬性,預設是 true:
這是啥意思呢?
這就是子組捕獲的語法。
如果不想捕獲子組,可以這樣寫 (?:aaa)
看,capturing 變為 false 了。
那捕獲和非捕獲有什麼區別呢?
我們試一下:
哦,原來 Group 的 capturing 屬性代表的是是否提取的意思啊。
我們通過 AST 可以看出來,捕獲是針對子組來說的,預設是捕獲,也就是提取子組的內容,可以通過 ?: 切換到非捕獲,就不會提取子組的內容了。
我們對用 AST 來了解正則語法已經輕車熟路了,那來看點難的:
正規表示式支援通過 (?=xxx) 的語法來表示先行斷言,用來判斷某個字串是否前面是某個字串。
通過 AST 可以看到這種語法叫做 Assertion,並且型別為 lookahead,也就是往前看,只匹配前面的意思:
這是啥意思呢?為啥要這麼寫?和 /bbb(ccc)/ 還有 /bbb(?:ccc)/有啥區別呢?
我們試一下:
從結果可以看出來:
/bbb(ccc)/ 匹配了 ccc 的子組並且提取出來了這個子組,因為預設子組是捕獲的。
/bbb(?:ccc)/ 匹配了 ccc 的子組但沒有提取出來,因為我們通過 ?: 設定了子組不捕獲。
/bbb(?=ccc)/ 匹配了 ccc 的子組也沒有提取出子組,說明也是非捕獲的。它和 ?: 的區別是 ccc 沒有出現在匹配結果裡。
這就是先行斷言(lookahead assertion)的性質:先行斷言代表某段字串前面是某段字串,對應的子組是非捕獲的,而且斷言的字串不會出現在匹配結果中。
如果後面不是跟著那段字串就不匹配:
把 ?= 改成 ?! 之後意思就變了,通過 AST 看一下:
雖然還是先行斷言 lookahead assertion,但是多了個 negative 為 true 的屬性。
這個意思很明顯,本來是前面是某段字串,否定之後就是前面不是某段字串。
那匹配結果正好就反過來了:
現在前面不是某段字串的話才匹配了,這就是否定先行斷言。
有先行斷言自然也有後行斷言,也就是後面是某段字串才匹配。
同理,也可以否定:
(?<=aaa)對應的 AST 很容易想到,就是 lookbehind assertion:
(?<!aaa)對應的 AST 就是加個 negative 屬性:
先行斷言、後行斷言就是最難理解的正規表示式語法了,通過 AST 來學習是不是就容易理解多了~
正規表示式是處理字串的很方便的工具,但它的學習還是有些難度的,像貪婪匹配、非貪婪匹配、捕獲子組、非捕獲子組、先行斷言、後行斷言等語法很多人都搞不清楚。
我推薦通過 AST 來學習正則,AST 是按照語法結構來組織的一顆物件樹,各種語法通過 AST 節點的名字和屬性可以輕易的理清楚。
比如我們通過 AST 理清楚了:
重複語法(Repetition)就是字元 + 量詞的形式,預設是貪婪匹配(greedy 為 true),代表一直匹配到不匹配為止,量詞後加個 ? 就切換成了非貪婪匹配,匹配到一個字元就停止。
子組語法(Group)是用於提取某段字串的,預設是捕獲(capturing 為 true),代表需要提取,可以通過 (?:xxx)切換到非捕獲,只匹配不提取。
斷言語法(Assertion)代表前面或後面有某段字串,分為先行斷言(lookahead assertion)和後行斷言(lookbehind assertion),語法分別是(?=xxx)和 (?<=xxx),可以通過把 = 換成 ! 來表示否定(negative 為 true),意思正好反過來。
是各種檔案對語法理解的深還是編譯器對語法理解的深?
那還用問,肯定是編譯器呀!
那麼通過它按照語法 parse 出來的語法樹來學習語法自然比檔案更好。
正規表示式是這樣,其他的語法的學習也是這樣,能用 AST 學會的語法,就不需要看檔案。
更多node相關知識,請存取:!
以上就是怎麼快速掌握正規表示式?通過 AST 來學學正則語法!的詳細內容,更多請關注TW511.COM其它相關文章!