正則位置匹配
先了解下以下幾個概念
-
零寬:只匹配位置,在匹配過程中,不佔用字元,所以被稱為零寬
-
先行:正則引擎在掃描字元的時候,從左往右掃描,匹配掃描指標未掃描過的字元,先於指標,故稱先行
-
後行:匹配指標已掃描過的字元,後於指標到達該字元,故稱後行,即產生回溯
-
正向:即匹配括號中的表示式
-
負向:不匹配括號中的表示式
es5 就支援了先行斷言
es2018 才支援後行斷言
零寬正向先行斷言,又稱正向向前查詢(positive lookhead)
注意:
.
在正則裡面代表匹配除換行符,回車符等少數空白字元之外的任何字元,匹配其時需要轉義
(?=pattern):某位置後面緊接著的字元序列要匹配 pattern
例:
`sinM.`.match(/sin(?=M\.)/g); // ["sin"]
`M.sin`.match(/sin(?=M\.)/g); // null
複製程式碼
第一個 sin 會匹配,因為他後面有 pattern
零寬負向先行斷言,又稱負向向前查詢(negative lookhead)
(?!pattern):某位置後面緊接著的字元序列不能匹配 pattern
例:
`M.sin`.match(/sin(?!M\.)/g); // ["sin"]
`sinM.`.match(/sin(?!M\.)/g); // null
複製程式碼
第一個 sin 會匹配,因為他後面沒有 pattern
零寬正向後行斷言,又稱正向向後查詢(positive lookbehind)
(?<=pattern):某位置前面緊接著的字元序列要匹配 pattern
例:
'sinM.'.match(/(?<=M\.)sin/g); // null
'M.sin'.match(/(?<=M\.)sin/g); // ["sin"]
複製程式碼
第二個 sin 會匹配,因為它前面有 pattern
零寬負向後行斷言,又稱負向向後查詢(negative lookbehind)
(?<!pattern):某位置前面緊接著的字元序列不能匹配 pattern
例:
'sinM.'.match(/(?<!M\.)sin/g); // ["sin"]
'M.sin'.match(/(?<!M\.)sin/g); // null
複製程式碼
第一個 sin 會匹配,因為它前面沒有 pattern
來看個實際的例子,把4+6*sqrt(5)*Math.sqrt(5)
轉換成可以通過eval
或者new Function()
獲得實際結果的字串
這個可以使用負向後行斷言,即替換前面不緊接 Math.的 sqrt 字串序列
let s = `4+6*sqrt(5)*Math.sqrt(5)`.replace(/(?<!Math\.)sqrt/g, func => `Math.${func}`);
eval(s); // 34
複製程式碼
第二個例子: 匹配 url 後面的路徑
'https://www.google.com/v3/api/getUser?user=panghu'.match(/(?<=\.\w*(?=\/)).*/);
複製程式碼
第三個例子:替換字串中 img 標籤的 width 為 100%
'<img id = "23" style="width:999x;"/><img id = "23" style="width:999x;"/>'.replace(
/(?<=(<img[\s\S]*width:\s*))[^("\/);]*/gm,
'100%'
);
複製程式碼
匹配 sin
'M.sin'.match(/(?<=M\.)sin/g); // ["sin"]
`M.sin`.match(/sin(?!M\.)/g); // ["sin"]
複製程式碼
這兩種方法都可以實現同樣的效果,但我個人更喜歡使用第一種方法,它的寫法更符合人的直接思維習慣
在全域性匹配修飾符 g 作用下正則 test 方法出現的“怪異”結果
先看下面兩行程式碼的執行結果
let reg = /js/g;
reg.test('js'); //before: lastIndex:0, after: lastIndex:2
reg.test('js'); //before: lastIndex:2, after: lastIndex:0
reg.test('js'); //before: lastIndex:0, after: lastIndex:2
複製程式碼
如果你的答案是三個 true 的話,那就錯了 答案其實是 true、false、true,這就是所謂的怪異現象
為什麼?答: RegExp 物件有個 lastIndex 屬性,它的初始值是 0, 當不使用 g 修飾符修飾時,每次執行 test 方法之後它都會自動置 0 而使用 g 修飾符時,每次執行 test 方法的時候,它都是從索引值為 lastIndex 的位置開始匹配,lastIndex 為匹配到的字元序列下一個索引值。只有當匹配失敗以後才會將 lastIndex 置為 0
例:上述例子中的第一個 test 方法執行之前,lastIndex 值為 0,執行之後 lastIndex 值為 2,於是當第二次執行 test 方法時,從字串索引值為 2 處開始匹配,顯然會匹配失敗,所以第三次匹配時又會匹配成功
匹配含 class 為 root 的標籤(不考慮特殊情況), 如<div class="root">
這裡可以涉及到的知識點有:貪婪/非貪婪匹配,模式匹配,回溯及其消除,分組,反向引用
基礎版:只匹配雙引號包裹的 class
`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class="root".*?>/g);
// ["<div class="root">", "<span class="root">"]
複製程式碼
模式匹配[^>]
表示匹配除[^]
裡面的所有字元,這裡就是匹配除>
外的所有字元
注意前後都需要非貪婪匹配符號?否則只有前面的,它會貪婪的吃掉 div;只有後面的,它會貪婪的吃掉 span
完整版:單雙引號包裹的 class 都可以匹配
`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class=("root"|'root').*?>/g);
// ["<div class="root">", "<span class="root">", "<i class='root'>"]
複製程式碼
這裡如果不使用[^>]
而使用.*
就會出現下面這種匹配結果,不是我們想要的
["<div class="root">", "<span class="root">", "</span><i class='root'>"]
進階版:使用分組引用消除難看的("root"|'root')
,再消除.*?
回溯
`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class=("|')root\1[^>]*>/g);
// ["<div class="root">", "<span class="root">", "<i class='root'>"]
複製程式碼
\1
表示引用前面的第一個分組結果,即("|')
的匹配結果,這樣就能保證單引號配對單引號,雙引號匹配雙引號
[^>]*
代替.*?
可以消除使用*?
引發的回溯,因為*
是儘可能多的匹配,而?
是儘可能少的匹配
回顧開頭,我所說的特殊情況就是標籤的屬性值不能含有>
,因為為了消除回溯使用的[^>]
含有字元>,這部分其實可以使用其他正則代替,讓它在消除回溯的情況下可以匹配特殊情況
如果大家對匹配含 class 為 root 的標籤這部分涉及的知識點感興趣,可以在底下評論,我到時候再仔細講
如果你喜歡這篇文章的話,麻煩點個⭐原文地址資瓷下
參考:
JavaScript 權威指南(第 6 版)
Javascript 正規表示式迷你書
以上如有錯誤,歡迎指正