網上JS正則基礎教程沒有涉及的一些知識

涼涼涼涼發表於2018-08-05

 正則起源

  最近看完了 《精通正規表示式》,收穫頗豐,略過了一些晦澀難懂的理論部分,主要看了實戰和教程部分。

  下面引用一下百度百科裡的內容。

正規表示式的“鼻祖”或許可一直追溯到科學家對人類神經系統工作原理的早期研究。美國新澤西州的Warren McCulloch和出生在美國底特律的Walter Pitts這兩位神經生理方面的科學家,研究出了一種用數學方式來描述神經網路的新方法,他們創造性地將神經系統中的神經元描述成了小而簡單的自動控制元,從而作出了一項偉大的工作革新。

  那麼寫正則是不是就是把自己神經工作過程通過正則表現出來呢? 比如讓小孩子在一堆圖形中找到匹配的圖形放入凹槽。

 正則引擎

  正則分幾種引擎也從是本書獲得的知識點之一。

  • DFA
  • 傳統型NFA
  • POSIX NFA

  NFA範圍更廣,例如 JAVA, PHP, Ruby, .NET... 你是看不起我javascript所以才不列入的嗎?

  使用DFA的是flex, MySQL, lex, awk大部分版本… 實話說,除了mysql,都沒聽過。不過不用在意!

  兩個引擎的區別。

  • NFA 更注重表示式
  • DFA 文字主導

  通過書中裡例子說,NFA 用表示式來匹配文字,而 DFA 是文字來匹配文表示式。當寫好一個正則之後,NFA 是先檢查表示式,同時檢查文字是否匹配這個表示式。而 DFA 則是先掃描文字,然後處理表示式中的所有匹配可能,如果匹配失敗,就將這條可能的線,淘汰。所以這裡衍生一個概念就是回溯,NFA 有回溯,而 DFA 沒有。

 知識點

  作為一個菜鳥,正規表示式一直是書到用時方恨少的角色。平時都是能抄則抄,不能抄的時候乾著急,只能用 substr, indexOf, chatAt等等的方法實現功能,既不優雅也不夠裝逼。上網學習也都是菜鳥教程,W3school。然後下面說一下以上兩個基礎教程裡沒說到的知識點。

  括號捕獲與反向引用

  當你在正規表示式裡使用了 (),在表示式匹配時,它能記住或者說快取括號內匹配的結果,從而可以拿到括號內的結果,可以重複使用或者只需要括號內的結果,來剔除不需要的匹配內容。

// 我們經常會用 match 方法來匹配字串,結果是一個陣列,而不是最後的匹配結果,為什麼呢?看下面的例子
"abc".match(/(a)(b)(c)/) // ["abc", "a", "b", "c"]
"abc".match(/abc/) // ["abc"]

  可以看到,括號會快取括號裡匹配的內容,單獨列出來,那麼怎麼拿到括號內的內容呢,而不是通過 match 返回的結果拿,因為有時候我們需要在表示式裡使用捕獲的值,從而達到匹配重複的內容。這部分就叫反向引用。

"abc-abc-cba".replace(/(a)(b)c-\1\2/, '') // c-cba
"abc-abc-cba".replace(/(a)(b)c/g, '$1$2') // ab-ab-cba
RegExp.$1 // a
RegExp.$2 // b

  這裡展示了兩種使用反向引用的方法,一種是在表示式內通過 \1\2 的形式拿到兩個快取的值,一種是使用 $1$2的形式拿到。因為正則是從左開始匹配的,所以 (a) 就是第一個捕獲的匹配值,所以他是\1 或是 $1,以此類推。

  非捕獲型括號

  上面說了括號會捕獲值,一般來說這樣會影響效能,或者你會用到括號來做分組,但是不想捕獲的情況,(?:)非捕獲型括號就是這麼用的,那麼重寫一下上面的例子。

"abc-abc-cba".replace(/(a)(?:b)c-\1\2/, '') // 匹配失敗了,因為\2不存在
"abc-abc-cba".replace(/(a)(?:b)c-\1/, '') // bc-cba
RegExp.$1 // a
RegExp.$2 // ""

  環視

 

型別 正規表示式
肯定逆序環視 ?<=
否定逆序環視 ?<!
肯定順序環視 ?=
否定順序環視 ?!

  ?= 和 ?! 在菜鳥和w3school 裡有簡單的提及,菜鳥裡還提到這兩個還能重寫捕獲,但是 ?<= 和 ?<! 並沒有提及。

  寫幾個 demo 表示一下:

// 找一個字母 a ,它緊跟在 b 前面
"abac".replace(/a(?=b)/g, '') // bac

// 找到一個字母 a ,它緊跟在一個不是 b 的字母前面
"abac".replace(/a(?!b)/g, '') // abc

// 接著是逆序環視
// 找到一個字母 a ,它跟在 b 後面
"abac".replace(/(?<=b)a/g, '') // abc

// 找到一個字母 a ,他不跟在 b 後面
"abac".replace(/(?<!b)a/g, '') // bac

// 一個有趣匹配
// 在 a 和 b 之間插入一個 ","
"abac".replace(/(?<=a)(?=b)/g, ",") // a,bac

  可以看出,環視是要和捕獲括號一起用的,並且不會佔用匹配字元,他只是檢查表示式是否匹配。所以這就是重寫捕獲了。

  忽略優先量詞

  量詞匹配一般有三種 *、 +、?。然而還可以寫作, *? 或 +? ,使匹配結果導向完全不同的結果。例子:

"abc-aaa-abc-abc".replace(/abc-.*-abc/, '') // ""

"abc-aaa-abc-abc".replace(/abc-.*?-abc/, '') // "-abc"

  *? 忽略優先會先忽略當前匹配的值,先匹配後面的 -abc,如果匹配失敗,再匹配自己,而 *會優先匹配自己,等匹配結束之後,再從後面一點點吐出,回來匹配量詞後面的表示式。從而造成以上不同的結果。知道這個之後,就不會再傻傻的把 * 和 ? 分開解讀了。當然,具體情況具體分析,到底使用哪個。

  回溯

  回溯應該算是正則裡的效能殺手了吧。如果表示式寫的不好,造成過度的災難性回溯,會導致執行時間指數級增長。具體情況還是通過搜尋引擎瞭解吧,解釋起來會過長,而且作為正則新手的我還不一定能解釋清楚。。。

 最後

  以上是我在《精通正規表示式》一書中得到的一些收穫,希望能分享給大家,如有錯誤歡迎指正。下一步呢就是去做一些練習來鞏固一下了。

相關文章