概述
本文主要通過介紹正則表達式中的一些進階內容,讓讀者瞭解正則表達式在日常使用中用到的比較少但是又比較重要的一部分內容,從而讓大家對正則表達式有一個更加深刻的認識。
本文的主要內容爲:
- 正則表達式回溯法原理
- 正則表達式操作符優先級
本文不介紹相關正則表達式的基本用法,如果對正則表達式的基本使用方法還不瞭解的同學,可以閱讀我的上一篇博客——正則表達式語法入門。
回溯法原理
回溯是影響正則表達式效率的一個非常重要的原因,我們在進行正則表達式匹配時,一定要儘可能的避免回溯。
很多人可能只是對聽說過“回溯法”,並不瞭解其中具體內容和原理,接下來就先讓我們看下什麼是回溯法。
回溯法的定義
回溯法就是指正則表達式從頭開始依次進行匹配,如果匹配到某個特定的情況下時,發現無法繼續進行匹配,需要回退到之前匹配的結果,選擇另一個分支繼續進行匹配中的現象。這個描述可能有點抽象,我們舉一個簡單的例子,讓大家能夠更加明確的理解回溯法:
const reg = /ab{1,3}c/;
const str = 'abbc';
// 第1步:匹配/a/,得到'a'
// 第2步:匹配/ab{1}/,得到'ab'
// 第3步:匹配/ab{2}/,得到'abb'
// 第4步:匹配/ab{3}/,匹配失敗,需要進行回溯
// 第5步:回溯到/ab{2}/,繼續匹配/ab{2}c/,得到'abbc'
// 第6步:正則表達式匹配完成,得到'abbc'
如果我們把正則表達式的各個分支都整理成一棵樹的話,正則表達式的匹配其實就是一個深度優先搜索算法。而回溯其實就是在進行深度優先匹配失敗後的後退正常操作邏輯。
如果退回到了根節點仍然無法匹配的話,就會將index向後移動一位,重新構建匹配數。即/bc/
在'abc'
時,由於第一個字符'a'
無法匹配,則移動到'b'
開始匹配。
回溯法產生場景
理解了回溯法和回溯操作,接下來我們來看下什麼場景下會出現回溯。出現回溯的場景主要有以下幾種:
- 貪婪量詞(貪婪匹配)
- 惰性量詞(非貪婪匹配)
- 分支結構(分支匹配)
接下來,讓我們一個一個來看下這些場景是如何出現回溯的。
貪婪量詞(貪婪匹配)
const reg = /ab{1,3}c/;
const str = 'abbc';
// 第1步:匹配/a/,得到'a'
// 第2步:匹配/ab{1}/,得到'ab'
// 第3步:匹配/ab{2}/,得到'abb'
// 第4步:匹配/ab{3}/,匹配失敗,需要進行回溯
// 第5步:回溯到/ab{2}/,繼續匹配/ab{2}c/,得到'abbc'
// 第6步:正則表達式匹配完成,得到'abbc'
最開始的例子其實就是一個貪婪匹配的示例,通過儘可能多的匹配b
從而導致了回溯。
惰性量詞(非貪婪匹配)
const reg = /ab{1,3}?c/;
const str = 'abbc';
// 第1步:匹配/a/,得到'a'
// 第2步:匹配/ab{1}/,得到'ab'
// 第3步:匹配/ab{1}c/,匹配失敗,需要進行回溯
// 第4步:回溯到/ab{1}/,繼續匹配/ab{2}/,得到'abb'
// 第5步:匹配/ab{2}c/,得到'abbc'
// 第6步:正則表達式匹配完成,得到'abbc'
與貪婪匹配類似,非貪婪匹配雖然每次都是去最小匹配數目,但是也會出現回溯的情況。
分支結構(分支匹配)
const reg = /(ab|abc)d/;
const str = 'abcd';
// 第1步:匹配/ab/,得到'ab'
// 第2步:匹配/abd/,匹配失敗,需要進行回溯
// 第3步:回溯到//,繼續匹配/abc/,得到'abc'
// 第4步:匹配/abcd/,得到'abcd'
// 第5步:正則表達式匹配完成,得到'abcd'
通過上面的示例我們可以看到,分支結構在出現兩個分支情況類似的時候,也會出現回溯的情況,在這種情況下,如果一個分支無法匹配,則會回到這個分支的最初情況來重新進行匹配。
正則表達式操作符優先級
看完了回溯法,下面我們來了解下關於正則表達式操作符的優先級。
我們直接看結論,然後再根據結論來給大家提供示例進行理解。
操作符描述 | 操作符 | 優先級 | |
---|---|---|---|
轉移符 | \ | 1 | |
小括號和中括號 | (…)、(?:…)、(?=…)、(?!…)、[…] | 2 | |
量詞限定符 | {m}、{m,n}、{m,}、?、*、+ | 3 | |
位置和序列 | ^、$、元字符、一般字符 | 4 | |
管道符 | \ | 5 |
通過操作符的優先級,我們能夠知道如何來讀一個正則表達式。以下面這個正則表達式爲例,我們來介紹一下按照優先級進行分析的方法:
const reg = /ab?(c|de*)+|fg/;
// 第一步,根據優先級先考慮(c|de*)+,再根據優先級拆分得到c de*,即匹配c或者de*(注意,位置和序列的優先級高於管道符|,所以是c或de*而不是c或d和e*)
// 第二步,得到ab?,根據優先級拆分得到a和b?
// 第三步,得到fg,這個內容和第一步+第二步的結果爲或的關係
最終,我們得到的效果如下:
通過這個圖,大家就能夠理解我們的分析思路:先找括號,括號中的一定爲一個整體(轉移符只做轉義,不分割正則,因此可以認爲第一優先級其實是括號),沒有括號後再從左到右按照優先級進行分析。量詞限定符則看做是正則的一個整體。
注:如果大家需要話類似的正則表達式流程圖,可以使用此網站。
根據上面的優先級,我們就能夠避免在正則表達式的理解中出現歸類錯誤的情況。
總結
本文通過介紹在正則表達式中容易被忽略的兩個內容:回溯法
和操作優先級
,讓大家能夠在進行正則的閱讀和書寫過程中避免踩到相關的坑。
參考內容
- 《JavaScript正則表達式迷你書》——老姚 V1.1
- 《JavaScript權威指南》
我的博客即將搬運同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/dev...