前端正規表示式學習和實踐

熊貓7發表於2019-02-20

前言

最近的專案裡有多處格式和查詢使用到了正則表達。簡單的正則語法會寫了,但是一直沒有花功夫研究遇到的複雜正則。剛好趁著這一波研究的興趣,總結一下自己學習到的東西,不再沒有靈魂的 google 後複製貼上了。以後再看別人寫的優雅程式碼,再也不用眼饞了。

推薦工具

  1. regex用來測試
  2. regexper用來解析

regexper的解析真的非常有用處,它可以圖形化的展示出一個正則的所有的邏輯分支。對於自己難以讀懂的長正則,或者自己寫的複雜正則,都可以先解析成圖形化的邏輯來分析,大大降低了難度。如下圖隨便寫的一個正則解析:

前端正規表示式學習和實踐

正規表示式基礎知識

字元類

字元 匹配 字元 匹配
[] 方括號內的任何字元 [^] 不在方括號內的任何字元
\w [a-zA-Z0-9] \W [^a-zA-Z0-9]
\s 任何Unicode空白字元 \S 任何Unicode空白字元
\d [0-9] \D [^0-9]
. 除了換行符和其它Unicode終止符之外的任意字元 \b 單詞的邊界。\w和\W之間的邊界。

重複

字元 含義
{n,m} n <= matches < m
{n,} matches >= n
{n} matches === n
? {0,1}
+ {1,}
* {0,}

注意:

*? 可以匹配0個字元,因此它們允許什麼都不匹配。

/a*/.test('bbbb')   //=> true
複製程式碼

非貪婪重複

正常匹配重複字元是儘可能多的匹配,我們稱之為“貪婪的”匹配。

非貪婪的匹配級儘可能的少匹配。只需在待匹配字元後加一個問號

// 貪婪匹配
'bbbb'.match(/b+/g)   // => ["bbbb"]
// 非貪婪匹配
'bbbb'.match(/b+?/g)   // => ["b", "b", "b", "b"]
複製程式碼

正規表示式匹配總是會尋找字串中第一個可能匹配的位置。因此不會考慮它子串中更短的匹配。

'aaab'.match(/a+b/g)  // => ["aaab"]
// 你可能想得到 ’ab‘, 但是並不會。
'aaab'.match(/a+?b/g)  // => ["aaab"]
複製程式碼

選擇

字元 含義
| 或關係

引用

字元 含義
() 1. 把單獨的項合成子表示式,以便像處理一個獨立單元那樣使用。
2. 在完整模式中定義子模式。當一個正規表示式成功和目標字串匹配時,可以從目標串中抽出子模式匹配的部分。
3. 允許在同一個正規表示式的後面引用前面的子表示式。\1 引用的是第一個帶圓括號的子表示式。 \3引用的是第三個。因為可以巢狀,所以是按參與計數的左括號的位置來決定。
\n 和第 n 個分組匹配的字串匹配
\k 在語法模式中呼叫命名的分組,同\n類似

引用的一個好處就是並不是子表示式相同,而是與引用子表示式模式匹配的文字相等。即一個字串中各個部分包含的是完全相同的字串。

例如匹配單引號或雙引號:

// 它並不要求左右 單雙引號 是匹配的
const partten = /['"][^'"]*['"]/g
// 改為
const partten = /(['"])[^'"]*\1/g
複製程式碼

分組

字元 含義
() 可以記住和這個組合匹配的字串以供以後的引用使用
(?:) 單純分組,不記住與該組合匹配的字串
(? ) 將分組以 name 命名

錨點

類似 \b不匹配任何字元,指定匹配發生的合法位置。有時我們成為正規表示式的錨。

字元 含義
\b 單詞邊界(a word boundary),\w和\W之間的邊界,或位於一個單詞與字串開始和結束之間的邊界。
\B 非單詞邊界(Non-word boundary)
^ 字串的開始
$ 字串的結束

前後關聯約束

字元 含義
(?=) 前置約束-存在
(?!) 前置約束-排除
(?<=) 後置約束-存在
(?<!) 後置約束-排除

修飾符

字元 含義
i 執行不區分大小寫
g 找到所有匹配,否則在找到第一個後就停止
m 多行匹配,^匹配一行的開頭,$匹配一行的結束
s (es2018) 使.匹配所有字元,包括換行符

JavaScript基礎知識

用於模式匹配的 string 方法

String.prototype.search()

返回第一個滿足條件的匹配結果在整個字串中的位置。如果沒有任何匹配,則返回-1。
注意: 不支援全域性搜尋。只返回第一個匹配的位置資訊。

"This is a test text".search(/th/i)  // => 0
複製程式碼

String.prototype.replace()

字串物件的replace方法可以替換匹配的值。它接受兩個引數,第一個是正規表示式,表示搜尋模式,第二個是替換的內容。

var str = '  #id div.class  ';

str.replace(/^\s+|\s+$/g, '')
// "#id div.class"
複製程式碼

replace方法的第二個引數可以使用美元符號$,用來指代所替換的內容。

符號 含義
$& 匹配的子字串
$` 匹配結果前面的文字
$’ 匹配結果後面的文字
$n 匹配成功的第n組內容,n從1開始。
$ 匹配成功的命名組內容
$$ 指代美元 $

replace方法的第二個引數還可以是一個函式,將每一個匹配內容替換為函式返回值。

var a = 'The quick brown fox jumped over the lazy dog.';

a.replace(pattern, function replacer(match) {
  return match.toUpperCase();
});
複製程式碼

String.prototype.match()

返回匹配的陣列或null。 g 修飾符有效。

String.prototype.split()

str.split(separator, [limit])
複製程式碼

RegExp物件

RegExp.prototype.test()

返回布林型別

如果正規表示式帶有g修飾符,則每一次test方法都從上一次結束的位置開始向後匹配。

帶有g修飾符時,可以通過正則物件的lastIndex屬性指定開始搜尋的位置。

var r = /x/g;
var s = '_x_x';

r.lastIndex = 4;
r.test(s) // false
複製程式碼

如果正則模式是一個空字串,則匹配所有字串。

RegExp.prototype.exec()

正則例項物件的exec方法,用來返回匹配結果。如果發現匹配,就返回一個陣列,成員是匹配成功的子字串,否則返回null

var s = '_x_x';
var r1 = /x/;
var r2 = /y/;

r1.exec(s) // ["x"]
r2.exec(s) // null
複製程式碼

如果正則表示式包含圓括號(即含有“組匹配”),則返回的陣列會包括多個成員。第一個成員是整個匹配成功的結果,後面的成員就是圓括號對應的匹配成功的組。也就是說,第二個成員對應第一個括號,第三個成員對應第二個括號,以此類推。整個陣列的length屬性等於組匹配的數量再加1。

var s = '_x_x';
var r = /_(x)/;

r.exec(s) // ["_x", "x"]
複製程式碼

exec方法的返回陣列還包含以下兩個屬性:

  1. input:整個原字串。
  2. index:整個模式匹配成功的開始位置(從0開始計數)。

如果正規表示式加上g修飾符,則可以使用多次exec方法,下一次搜尋的位置從上一次匹配成功結束的位置開始。

ES2018 RegExp新特性

好吧,寫完這篇文章後,發現了ES 2018 的新特性文章都出來啦。。。於是趁熱,自己趕緊加上了。

1. 新增修飾符 s

點(.)是正規表示式模式中的特殊字元,它匹配除換行符之外的任何字元。 這就導致如果我們要匹配包括換行符在內的所有字元只能通過特殊方法實現。比如[\d\D]...

ES2018 引入了一種模式,其中點可用於實現相同的結果。可以使用s標誌在每個正規表示式的基礎上啟用此模式:

const regold = /test.test/
console.log(regold.test('test\ntest')) // => false

const  reg = /test.test/s
console.log(reg.test('test\ntest')) // => true
複製程式碼

2. 可命名組

符號 含義
(?<name> ) 將分組以 name 命名
\k<name> 在語法模式中呼叫命名的分組匹配的字串,同\n類似
$<name> 匹配成功的命名組內容

3. 支援後行斷言

好吧,原來一直沒支援啊。

4. Unicode 屬性轉義

字元 含義 示例
\p 匹配字串中的 Unicode 字元 /\p{Number}/u 匹配 Unicode 中的任何十進位制數 /\p{Alphabetic}/u 匹配任意 Unicode 字母字元
\P 否定模式

練習題

基礎知識已經寫的差不多了。記住使用說明的最好方式就是做做題啦。

  1. 與搜尋字串開始處的 3 個數字匹配。
  2. 與除 a、b 和 c 以外的任何字元匹配。
  3. '1234567'.match(/\d{1,3}/g)的結果。(貪婪匹配)
  4. 不以“th”開頭的單詞匹配。
  5. 去除字串首尾的空格。
  6. 三分位格式化一個數字。
  7. 對密碼應用以下限制:其長度必須介於 4 到 8 個字元之間,並且必須至少包含一個數字。
  8. 獲取url中的屬性對應的值

還有一些正則練習的網站:

regexone.com/

callumacrae.github.io/regex-tuesd…

如果大家知道有意思的正則題目,歡迎分享哇~

參考資料

  1. RegExp物件
  2. 正規表示式實踐篇
  3. learn-regex
  4. JavaScript權威指南(第六版)
  5. 每個 JavaScript 開發者都該瞭解的 ES2018 新特性
  6. 字元編碼筆記:ASCII,Unicode 和 UTF-8

相關文章