javascript
正则表达式的一些探索(偏JavaScript)
簡單的探索下正則表達式的相關知識,首先先了解下正則表達式的引擎和匹配過程區(qū)別,再試著掌握如何在場景中編寫正則表達式,再然后探索下根據(jù)上文已知的原理和編寫過程怎么去優(yōu)化正則表達式,最后給出一些js里正則相關的小擴展。
基礎及原理簡單介紹
了解一下正則表達式的正則引擎(正則表達式使用的理論模型是有窮自動機,具體實現(xiàn)稱為正則引擎)。
正則引擎分有DFA(確定型有窮自動機)的和NFA(非確定型有窮自動機)的實現(xiàn),根據(jù)編譯相關知識的描述,兩者是可以等價轉換的。NFA又分傳統(tǒng)型和POSIX標準,下面是三者一個簡單的對照表:
-/- 回溯 忽略優(yōu)先量詞 捕獲型括號 應用算法 DFA N N N 文本主導 傳統(tǒng)型NFA Y ? Y ? Y 表達式主導 POSIX NFA Y N ? Y 表達式主導回溯指的是:一個類似枚舉的搜索嘗試過程,在搜索嘗試過程中尋找問題的解,當在分歧點選擇一條路徑,記住另一/多條路徑供之后使用,在選擇路徑后的探索過程中發(fā)現(xiàn)不滿足求解條件時,就返回到分歧點,嘗試其他路徑(回溯遵循后進先出原則/LIFO-last in first out)。
忽略優(yōu)先量詞指的是:支持盡可能少的匹配結果。
捕獲型括號指的是:匹配并捕獲結果,獲取組號。
文本主導指的是:匹配搜索過程中的每個字符都對匹配過程進行控制(這樣的結果就是:雖然我不能回溯,但我速度快呀 - DFA的傲嬌...)。
表達式主導指的是:表達式中的控制權在不同的表達式元素之間轉換,然后與文本進行匹配。
三者還有一些其他的差異,如編譯階段的處理及匹配最左最長原則等,由于文章里以js的正則為準,就不做更多區(qū)別的介紹,詳情可參考書籍《精通正則表達式》,不過值得注意的是本書中的內容不一定完全適合各個語言或平臺中的正則表達式...
正則匹配的過程規(guī)則總結:
1. 優(yōu)先選擇最左端的匹配結果(匹配是從左到右開始的)
匹配先從需要查找的字符串的第一個字符嘗試匹配;
如果匹配到結果,則返回結果;
如果匹配不到結果,則就需要從字符串的第二個字符位置開始重新匹配;
以此類推,直到嘗試過所有的字符串位置作為匹配起始點進行匹配后,返回匹配失敗結果。
如 forever 中匹配是否存在 ever:
第一輪:從f開始匹配,fore不匹配ever;
第二輪:從o開始匹配,orev不匹配ever;
第三輪:從r開始匹配,reve不匹配ever;
第四輪:從e開始匹配,ever匹配ever,獲得ever匹配起始點(此處為3)的索引和匹配結果。
2. 標準量詞總是匹配優(yōu)先的(*、+、?、{m,n})
匹配優(yōu)先:盡可能多的匹配;忽略優(yōu)先:盡可能少的匹配。
標準量詞總是匹配優(yōu)先的,如:foo 針對表達式取 fo|foo(下面匹配基于JavaScript)
匹配優(yōu)先結果:foo,/f[o]{1,2}/
忽略優(yōu)先結果:fo,/f[o]{1,2}?/,加?后表示忽略優(yōu)先量詞
更多原理和匹配過程詳解可以參考文章"淺析正則表達式—(原理篇)"
在需求場景中構建正則表達式
要構建正則表達式,得先了解一些正則表達式的一些普通字符和元字符。這些可以參照"百度百科-正則表達式_符號",該表對于正則使用生疏的同學可以作字典參考,這里對于這些字符就不多做贅述。
以幾個例子來說明下在有正則需求的場景中該如何來寫表達式。
例子一:查詢句子中某些單詞出現(xiàn)的次數(shù)。
首先分析情況得出:先給出個大致形狀,/pattern/g ,經測試發(fā)現(xiàn):
"the weather's often cold in the north and windy in the east".match(/the/g).length // output 4輸出4,明顯不對,這時候還需要考慮單詞的邊界情況,需要在pattern兩邊加上限制\b,并且注意不需要匹配單詞邊界,于是得出:
/\bthe\b/g再測一下:
"the weather's often cold in the north and windy in the east".match(/\bthe\b/g).length // output 3正確。
例子二:比如需要給數(shù)字加 ",(逗號)" 形成貨幣格式的場景,如1234567,變成1,234,567。
首先分析情況得出:逗號應該加在"左邊存在數(shù)字,右邊的數(shù)字個數(shù)是3的倍數(shù)"。
這里用到正則的"非獲取匹配,反向肯定預查 : (?<=pattern)" 和 "非獲取匹配,正向肯定預查 : (?=pattern)",非獲取匹配指的是匹配但不獲取匹配值,正/反向肯定預查指的是正/反向匹配時在任何匹配條件的字符串開始處匹配查找字符串。
先反向肯定預查"左邊存在數(shù)字"的組合,給出(?<=\d);再正向肯定預查"右邊數(shù)字個數(shù)是3的倍數(shù)的組合",給出(?=(\d{3})+$);結合兩者,加上全局的匹配 g,得出:
/(?<=\d)(?=(\d{3})+$)/g測試一下效果:
'123456789'.replace(/(?<=\d)(?=(\d{3})+$)/g,',') // output 123,456,789 '23456789'.replace(/(?<=\d)(?=(\d{3})+$)/g,',') // output 23,456,789 '1234'.replace(/(?<=\d)(?=(\d{3})+$)/g,',') // output 1,234例子三:比如想把雷老板的Are you ok 歌詞處理一下。
首先不想替換掉"...",其次注意轉義字符,最后結合需要取的是引號內包含且非"的字符,于是得出:
/\"([^\"\.]*)\"/g測試一下:
var str = ` Are you ok? Auther Mr.Lei 1. "Thank you!" 2. "Are you ok?" 3. "Hello" 4. "Thank you" 5. "Thank you every much" 6. "How are you Indian Mi fans?" 7. "Do you like Mi 4i?" 8. "..." `;var reg = /\"([^\"\.]*)\"/g; var replaceStr = str.replace(reg,'\"???\"'); // output // Are you ok? // Auther Mr.Lei // 1. "???" // 2. "???" // 3. "???" // 4. "???" // 5. "???" // 6. "???" // 7. "???" // 8. "..."如何寫正則表達式的總結:先寫出明確的匹配條件,再根據(jù)需求去組合或添加細節(jié)。
正則表達式優(yōu)化的一些探索
《精通正則表達式》書中和網上查找了不少資料,并且根據(jù)對NFA的理解,列出了一些符合理論的優(yōu)化建議(不排除更多可能):
1. 減少或者優(yōu)化判斷分支;
2. 精確匹配條件(字符和量詞);
3. 限制匹配優(yōu)先的作用范圍,如+和*的區(qū)別,+減少了多選結構的回溯次數(shù);
4. 節(jié)省引用(使用非獲取匹配);
5. 將復雜的多條件正則拆分成多個簡單的單條件正則;
6. 錨點的優(yōu)化,^(a|b)而非(^a|^b);
7. 量詞等價轉換的效率差異(因語言而異);
8. 使用固化分組(在支持的情況下,js并不支持);
9. 使用變量存儲正則表達式(減少正則編譯過程);
10. 還有更多細節(jié)...
《精通正則表達式》的第五及第六章都有涉及這些優(yōu)化的介紹,網上google/baidu也是不少資料和案例。
根據(jù)其主要做的優(yōu)化過程大致可總結出以下幾點(不排除更多可能):
1. 減少匹配的回溯次數(shù),減少時間;
2. 節(jié)省引用(使用非獲取匹配),減少空間;
3. 正則表達式自身優(yōu)化以達到編譯最優(yōu);
4. 也許更多吧...
以上是書籍和網上給出的優(yōu)化建議,給出的案例大多是針對的perl和PHP的,在JavaScript中并不適用,為了驗證js里的正則如何優(yōu)化,下面是我給出的一些測試結果...
基于JavaScript的正則表達式...這一小節(jié)給不出完整的測試代碼,因為在多個瀏覽器和nodejs端跑了很多測試代碼,給定的情況是不同的...
但單獨某個端或者瀏覽器給出的結果是穩(wěn)定的,以下是給出Chrome和Firefox瀏覽器和Nodejs各跑100000次(原先是1W長度字符串跑1000次,后面為了效果更明顯最終增加為10W長度字符串跑10W次)測試的結果總結...還有Safari瀏覽器、360瀏覽器、QQ瀏覽器的測試結果也統(tǒng)一不了.
Chrome瀏覽器下:使用.、*這類元字符或范圍比精確查找條件的性能要優(yōu);非貪婪模式比貪婪模式性能優(yōu);獲取匹配比非獲取匹配性能優(yōu);優(yōu)化判斷分支(將最可能匹配的結果放前面)后性能更差;Firefox瀏覽器下:精確查找條件比使用.、*這類元字符或者范圍的性能優(yōu);非貪婪模式比貪婪模式性能優(yōu);獲取匹配比非獲取匹配性能優(yōu);優(yōu)化判斷分支(將最可能匹配的結果放前面)后性能更差;Nodejs環(huán)境下:使用.、*這類元字符或范圍比精確查找條件的性能優(yōu);貪婪模式比非貪婪模式性能優(yōu);獲取匹配比非獲取匹配性能優(yōu);優(yōu)化判斷分支(將最可能匹配的結果放前面)性能優(yōu);以上的測試結果并不能給出肯定的答案,所以建議在JavaScript里使用正則的時候先測一下性能以免導致不愉快的意外,至于JavaScript中正則表達式的實現(xiàn)過程更是需要看V8的代碼了...
JavaScript RegExp 小擴展
lastIndex 和 test 方法的愛恨情仇
var str = 'abcabc'; var regexp = /a/g;console.log(regexp.test(str)); // output true console.log(regexp.lastIndex); // output 1 console.log(regexp.test(str)); // output true console.log(regexp.lastIndex); // output 4console.log(regexp.test(str)); // output false console.log(regexp.lastIndex); // output 0
test 方法在執(zhí)行后將 lastIndex 屬性值置為匹配到的結果索引值,并返回匹配結果;下一次執(zhí)行 test 方法時從 lastIndex 位置開始匹配,并返回匹配結果;以此類推;最后一次執(zhí)行 test 方法,索引重置為0,匹配結果為false。
RegExp vs indexOf 誰才是快男
在固定匹配值和"只匹配一次"的條件下,針對判斷字符串是否包含某個"固定的"字符串,indexOf 優(yōu)于 regexp,性能將近一半。
var str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36";console.time('reg'); /Mac/.test(str); console.timeEnd('reg');console.time('indexOf'); str.indexOf('Mac'); console.timeEnd('indexOf');經過多次測試? reg 的平均值 是 indexOf 的近2倍(環(huán)境:chrome & nodejs);然而在其他匹配條件下,RegExp 更靈活,更方便,更全面。
關于String.prototype.indexOf方法內部代碼暫時不知道怎么去探究,根據(jù)以下文檔有解釋,但未明確的理解,參考文檔:
[ECMAScript 2015 (6th Edition, ECMA-262)]
[ECMAScript 1st Edition (ECMA-262)](初始定義)
注意:本文只是一次知識探討,僅供做參考作用,請勿以之為準,如果需要更進一步的學習請以書籍以及自我實踐所得為準。
總結
以上是生活随笔為你收集整理的正则表达式的一些探索(偏JavaScript)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UITextFeild的基本属性
- 下一篇: JavaScript异步基础