「前端篇」不再為正則煩惱

echa攻城獅 發佈 2020-05-25T23:58:13+00:00

做一個簡單的演示, 比如常見的密碼校驗: 密碼需要6位至18位,且只能為數字,字母,以及特殊字符,.*《Continuation 在 JS 中的應用「前端篇」》


作者:李一二

轉發連結:https://mp.weixin.qq.com/s/PmzEbyFQ8FynIlXuUL0H-g

前言

有不少朋友都為寫正則而頭疼,不過筆者早已不為正則而煩惱了。本文分享一些我處理正則表達式的一些經驗。全文分為正則利器,正則基礎內容,正則進階內容三個大塊。

正則利器

首先推薦兩個正則工具利器——分別是regex101(一個正則在線檢驗工具), Regulex(一個正則可視化工具)。這兩個工具,可以大幅減小寫正則的難度。(文章相關連結不可點擊的,請點擊文末的【閱讀原文】閱讀)做一個簡單的演示, 比如常見的密碼校驗: 密碼需要6位至18位,且只能為數字,字母,以及特殊字符,.*

先使用Regulex來在線編寫正則。藉助其將正則圖形化的功能,我們很容易來修改調整自己的正則。

當通過Regulex工具寫完正則過後,可以使用regex101來檢查自己寫的正則是否正確,以及匹配結果

當上面兩個流程走通過後,意味著該正則是符合我們需求且正確無誤的,就可以遷移入代碼中啦。

手撕正則

關於正則的相關知識,我強烈推薦這篇文章《正則表達式30分鐘入門教程》。接下來我會將正則內容與JavaScript穿插起來講。

JS創建正則表達式

在JS里有兩種創建正則表達式的方式,分別是字面量和調用RegExp對象的構造函數。使用一個正則表達式字面量,其由包含在斜槓之間的模式組成,如下所示:

var re = /ab+c/;

腳本加載後,正則表達式字面量就會被編譯。當正則表達式保持不變時,使用此方法可獲得更好的性能。

或者調用RegExp對象的構造函數,如下所示:

var re = new RegExp("ab+c");

在腳本運行過程中,用構造函數創建的正則表達式會被編譯。 如果正則表達式將會改變,或者它將會從用戶輸入等來源中動態地產生,就需要使用構造函數來創建正則表達式。

總結一下,兩種模式語法如下:

/pattern/flags
new RegExp(pattern [, flags])
RegExp(pattern [, flags])

參數如下:
pattern
    正則表達式的文本。

flags
    如果指定,標誌可以具有以下值的任意組合:

    g
    全局匹配;找到所有匹配,而不是在第一個匹配後停止
    
    i
    忽略大小寫
    
    m
    多行; 將開始和結束字符(^和$)視為在多行上工作(也就是,分別匹配每一行的開始和結束(由 \n 或 \r 分割),而不只是只匹配整個輸入字符串的最開始和最末尾處。
    
    u
    Unicode; 將模式視為Unicode序列點的序列
    
    y
    粘性匹配; 僅匹配目標字符串中此正則表達式的lastIndex屬性指示的索引(並且不嘗試從任何後續的索引匹配)。
    
    s
    dotAll模式,匹配任何字符(包括終止符 '\n')。

上面參數提到的全局匹配,粘性匹配的知識在本文後面將做細緻的講解。

基礎知識

這裡先了解一些基礎的正則知識,不做過多的講解。

常用元字符



常用限定符



常用的反義代碼



JS里的一些正則函數

在 JavaScript中,正則表達式也是對象。這些模式被用於 RegExp 的 exec 和 test 方法, 以及 String 的 match、matchAll、replace、search 和 split 方法。

test

test() 方法執行一個檢索,用來查看正則表達式與指定的字符串是否匹配。返回 true 或 false。

var str = 'hello world!';
var result = /^hello/.test(str);
console.log(result);
// true

exec

exec() 方法在一個指定字符串中執行一個搜索匹配。返回一個結果數組或 null。

在設置了 global 或 sticky 標誌位的情況下(如 /foo/g or /foo/y),JavaScript RegExp 對象是有狀態的。他們會將上次成功匹配後的位置記錄在 lastIndex 屬性中 。使用此特性,exec() 可用來對單個字符串中的多次匹配結果進行逐條的遍歷(包括捕獲到的匹配),而相比之下, String.prototype.match() 只會返回匹配到的結果。

const regex1 = RegExp('foo*', 'g');
const str1 = 'table football, foosball';
let array1;

while ((array1 = regex1.exec(str1)) !== null) {
  console.log(`Found ${array1[0]}. Next starts at ${regex1.lastIndex}.`);
  // expected output: "Found foo. Next starts at 9."
  // expected output: "Found foo. Next starts at 19."
}

上面提到了正則表達式的global和sticky 兩種模式,這裡我詳細講下這兩種模式。

全局模式

global這裡是指全局匹配——找到所有匹配,而不是在第一個匹配後停止。用前面提到的密碼正則^[\da-zA-Z,.*]{6,18}$來舉個例子:

# 非全局模式
var keyword = '2020myTestkeyword,';
var reg = /^[\da-zA-Z,.*]{6,18}$/i;   // (i , 忽略大小寫)

console.log(reg.lastIndex, reg.test(keyword)); // 輸出0 true
console.log(reg.lastIndex, reg.test(keyword)); // 輸出0 true
console.log(reg.lastIndex, reg.test(keyword)); // 輸出0 true
console.log(reg.lastIndex, reg.test(keyword)); // 輸出0 true

---------------------

# 全局模式
var keyword = '2020myTestkeyword,';
var regG = /^[\da-zA-Z,.*]{6,18}$/ig;   // (i , 忽略大小寫)

console.log(regG.lastIndex, regG.test(keyword)); // 輸出0 true
console.log(regG.lastIndex, regG.test(keyword)); // 輸出18 false
console.log(regG.lastIndex, regG.test(keyword)); // 輸出0 true
console.log(regG.lastIndex, regG.test(keyword)); // 輸出18 false

上面的兩段代碼可以看到在全局模式下,正則的匹配結果為,true和false依次交替。這是因為: 在全局匹配模式下可以對指定要查找的字符串執行多次匹配。每次匹配使用當前正則對象的lastIndex屬性的值作為在目標字符串中開 始查找的起始位置。lastIndex屬性的初始值為0,找到匹配的項後lastIndex的值被重置為匹配內容的下一個字符在字符串中的位置索引,用來標識下次執行匹配時開始查找的位置。如果找不到匹配的項lastIndex的值會被設置為0。當沒有設置正則對象的全局匹配標誌時lastIndex屬性的值始終為0,每次執行匹配僅查找字符串中第一個匹配的項。 lastIndex, 是一個可讀可寫的屬性。需要特別注意是的,如前所述,正則表達當指定了g(全局匹配)或者y(粘性匹配),均會有這個效果。

全局匹配的一個使用場景——循環遍歷,獲取所有匹配。

var result = null;
while ((result = globalre.exec(str)) != null)
{
        console.log(result[0]);
        console.log(globalre.lastIndex);
}


當然使用matchAll()方法也可以實現該效果,這裡不再贅述。


粘性匹配

sticky 屬性反映了搜索是否具有粘性( 僅從正則表達式的 lastIndex 屬性表示的索引處搜索 )。sticky是正則表達式對象的只讀屬性。sticky 的值是 Boolean ,並在「y」標誌使用時為真; 否則為假。


var str = '#foo#';
var regex = /foo/y;

regex.lastIndex = 1;
regex.test(str); // true (譯註:此例僅當 lastIndex = 1 時匹配成功,這就是 sticky 的作用)
regex.lastIndex = 5;
regex.test(str); // false (lastIndex 被 sticky 標誌考慮到,從而導致匹配失敗)
regex.lastIndex; // 0 (匹配失敗後重置)


進階知識


接下來我會講一下,正則裡面比較進階且我覺得比較重要的內容。如前所述,這部分內容,我強烈推薦閱讀這篇文章正則表達式30分鐘入門教程。為了不做重複工作,這裡我會講得比較簡略。


分支

所謂分支,是指有幾種規則,如果滿足其中任意一種規則都應該當成匹配,具體方法是用|把不同的規則分隔開。0\d{2}-\d{8}|0\d{3}-\d{7}


分組

所謂分組,類似於編碼中的表達式的功能。用小括號來指定子表達式(也叫做分組),然後你就可以指定這個子表達式的重複次數了。如下所示(\d{1,3}\.){3}\d{1,3}


貪婪與懶惰

當正則表達式中包含能接受重複的限定符時,通常的行為是(在使整個表達式能得到匹配的前提下)匹配儘可能多的字符。以這個表達式為例:a.*b,它將會匹配最長的以a開始,以b結束的字符串。如果用它來搜索aabab的話,它會匹配整個字符串aabab。這被稱為貪婪匹配。

有時,我們更需要懶惰匹配,也就是匹配儘可能少的字符。前面給出的限定符都可以被轉化為懶惰匹配模式,只要在它後面加上一個問號?即可。懶惰限定符如下



a.*?b匹配最短的,以a開始,以b結束的字符串。如果把它應用於aabab的話,它會匹配aab(第一到第三個字符)和ab(第四到第五個字符)。

斷言

接下來的四個用於查找在某些內容(但並不包括這些內容)之前或之後的東西, 也就是說它們像\b,^,$那樣用於指定一個位置,這個位置應該滿足一定的條件(即斷言),因此它們也被稱為零寬斷言。

先行斷言

x(?=exp),先行斷言: exp緊跟x的情況下匹配x。例如,對於/Jack(?=Sprat)/,「Jack」在跟有「Sprat」的情況下才會得到匹配./Jack(?=Sprat)/ 「Jack」後跟有「Sprat」或「Frost」的情況下才會得到匹配。不過, 匹配結果不包括「Sprat」或「Frost」


let regex = /First(?= test)/g;

console.log('First test'.match(regex)); // [ 'First' ]
console.log('First peach'.match(regex)); // null
console.log('This is a First test in a year.'.match(regex)); // [ 'First' ]
console.log('This is a First peach in a month.'.match(regex)); // null

負向先行斷言

x(?!exp),負向先行斷言: x後無exp緊隨的情況下匹配x。例如,對於/\d+(?!\。)/,數字後沒有跟隨小數點的情況下才會得到匹配。對於/\d+(?!\.)/.exec(3.141),匹配『141』而不是『3』。


console.log(/\d+(?!\.)/g.exec('3.141')); // [ '141', index: 2, input: '3.141' ]

後行斷言

(?<=exp)x,後行斷言: x緊隨exp的情況下匹配x。例如,對於/(?<=Jack)Sprat/,「Sprat」緊隨「Jack」時才會得到匹配。對於/(?<=Jack|Tom)Sprat,「Sprat」在緊隨「Jack」或「Tom」的情況下才會得到匹配。不過,匹配結果中不包括「Jack」或「Tom」。

let oranges = ['ripe orange A ', 'green orange B', 'ripe orange C',];

let ripe_oranges = oranges.filter( fruit => fruit.match(/(?<=ripe )orange/));
console.log(ripe_oranges); // [ 'ripe orange A ', 'ripe orange C' ]

負向後行斷言

(?<!exp)x,負向後行斷言: x不緊隨y的情況下匹配x。例如,對於/(?<!-)\d+/,數字緊隨-符號的情況下才會得到匹配。對於/(?<!-)\d+/.exec(3) ,「3」得到匹配。而/(?<!-)\d+/.exec(-3)的結果無匹配,這是由於數字之前有-符號。


斷言實戰

提取字符串123sadf21(aaaaf213sdaf),括號中的部分。

如上圖所示,(?<=\().+(?=\)), 該正則分為三部分:(?<=\(,這是後行斷言;(?=\)), 這是先行斷言;.+,匹配任意長的字符。

寫在最後

以上就是本文的所有內容。正則使用得好,能起到事半功倍的作用。利用好本文推薦的工具,多加練習,相信正則一定不再是煩惱!

推薦JavaScript經典實例學習資料文章

《「速圍」Node.js V14.3.0 發布支持頂級 Await 和 REPL 增強功能》

《深入細品瀏覽器原理「流程圖」》

《JavaScript 已進入第三個時代,未來將何去何從?》

《前端上傳前預覽文件 image、text、json、video、audio「實踐」》

《深入細品 EventLoop 和瀏覽器渲染、幀動畫、空閒回調的關係》

《推薦13個有用的JavaScript數組技巧「值得收藏」》

《前端必備基礎知識:window.location 詳解》

《不要再依賴CommonJS了》

《犀牛書作者:最該忘記的JavaScript特性》

《36個工作中常用的JavaScript函數片段「值得收藏」》

《Node + H5 實現大文件分片上傳、斷點續傳》

《一文了解文件上傳全過程(1.8w字深度解析)「前端進階必備」》

《【實踐總結】關於小程序掙脫枷鎖實現批量上傳》

《手把手教你前端的各種文件上傳攻略和大文件斷點續傳》

《字節跳動面試官:請你實現一個大文件上傳和斷點續傳》

《談談前端關於文件上傳下載那些事【實踐】》

《手把手教你如何編寫一個前端圖片壓縮、方向糾正、預覽、上傳插件》

《最全的 JavaScript 模塊化方案和工具》

《「前端進階」JS中的內存管理》

《JavaScript正則深入以及10個非常有意思的正則實戰》

《前端面試者經常忽視的一道JavaScript 面試題》

《一行JS代碼實現一個簡單的模板字符串替換「實踐」》

《JS代碼是如何被壓縮的「前端高級進階」》

《前端開發規範:命名規範、html規範、css規範、js規範》

《【規範篇】前端團隊代碼規範最佳實踐》

《100個原生JavaScript代碼片段知識點詳細匯總【實踐】》

《關於前端174道 JavaScript知識點匯總(一)》

《關於前端174道 JavaScript知識點匯總(二)》

《關於前端174道 JavaScript知識點匯總(三)》

《幾個非常有意思的javascript知識點總結【實踐】》

《都2020年了,你還不會JavaScript 裝飾器?》

《JavaScript實現圖片合成下載》

《70個JavaScript知識點詳細總結(上)【實踐】》

《70個JavaScript知識點詳細總結(下)【實踐】》

《開源了一個 JavaScript 版敏感詞過濾庫》

《送你 43 道 JavaScript 面試題》

《3個很棒的小眾JavaScript庫,你值得擁有》

《手把手教你深入鞏固JavaScript知識體系【思維導圖】》

《推薦7個很棒的JavaScript產品步驟引導庫》

《Echa哥教你徹底弄懂 JavaScript 執行機制》

《一個合格的中級前端工程師需要掌握的 28 個 JavaScript 技巧》

《深入解析高頻項目中運用到的知識點匯總【JS篇】》

《JavaScript 工具函數大全【新】》

《從JavaScript中看設計模式(總結)》

《身份證號碼的正則表達式及驗證詳解(JavaScript,Regex)》

《瀏覽器中實現JavaScript計時器的4種創新方式》

《Three.js 動效方案》

《手把手教你常用的59個JS類方法》

《127個常用的JS代碼片段,每段代碼花30秒就能看懂-【上】》

《深入淺出講解 js 深拷貝 vs 淺拷貝》

《手把手教你JS開發H5遊戲【消滅星星】》

《深入淺出講解JS中this/apply/call/bind巧妙用法【實踐】》

《手把手教你全方位解讀JS中this真正含義【實踐】》

《書到用時方恨少,一大波JS開發工具函數來了》

《乾貨滿滿!如何優雅簡潔地實現時鐘翻牌器(支持JS/Vue/React)》

《手把手教你JS 異步編程六種方案【實踐】》

《讓你減少加班的15條高效JS技巧知識點匯總【實踐】》

《手把手教你JS開發H5遊戲【黃金礦工】》

《手把手教你JS實現監控瀏覽器上下左右滾動》

《JS 經典實例知識點整理匯總【實踐】》

《2.6萬字JS乾貨分享,帶你領略前端魅力【基礎篇】》

《2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】》

《簡單幾步讓你的 JS 寫得更漂亮》

《恭喜你獲得治療JS this的詳細藥方》

《談談前端關於文件上傳下載那些事【實踐】》

《面試中教你繞過關於 JavaScript 作用域的 5 個坑》

《Jquery插件(常用的插件庫)》

《【JS】如何防止重複發送ajax請求》

《JavaScript+Canvas實現自定義畫板》

《Continuation 在 JS 中的應用「前端篇」》

作者:李一二

轉發連結:https://mp.weixin.qq.com/s/PmzEbyFQ8FynIlXuUL0H-g

關鍵字: