JavaScript 正則入門到掌握
最近學習了 AST 抽象語法樹,在程式碼解析的過程中廣泛使用了正則表示式,由此認識到自己在正則基礎方面的薄弱,雖然清楚每個符號所表示的含義,但是當一大串正則符號出現在自己面前時,還是會懵逼一會,無法融匯貫通的掌握正則。
下面是自己整理的正則基礎知識和收集到的一些實戰訓練。希望通過訓練和筆記來加深自己對正則表示式的掌握!
基礎
正則表示式(Regular Expression,在程式碼中常簡寫為regex、regexp或RE)使用單個字串來描述、匹配一系列符合某個句法規則的字串搜尋模式。
搜尋模式可用於文字搜尋和文字替換。
下面是正則的基礎語法,只要熟悉就行,重要還是結合實際多運用正則,才能真正的掌握正則。
建立正則表示式
方式1:
// 語法格式為: /正則表示式主體/修飾符(可選) // 案例 var patt = /var/i // var 是正則表示式主體 // i 是修飾符 複製程式碼
方式2:
// 建立 RegExp 物件 // new RegExp(正則表示式主體[, 修飾符]) // RegExp(正則表示式主體[, 修飾符]) var patt = new RegExp('var', 'i') 複製程式碼
注意:/var/i
創鍵的正則表示式物件和new RegExp(var, i)
建立的物件並不相等。雖然它們匹配規則一樣。
指定匹配模式的修飾符有:
修飾符 | 描述 |
---|---|
i | 執行對大小寫不敏感的匹配。 |
g | 執行全域性匹配(查詢所有匹配而非在找到第一個匹配後停止)。 |
m | 執行多行匹配。 |
s |
允許.
去匹配多行 |
u | Unicode; 將模式視為Unicode序列點的序列Unicode; |
y |
粘性匹配; 僅匹配目標字串中此正則表示式的lastIndex
屬性指示的索引(並且不嘗試從任何後續的索引匹配)。 |
字元類別(Character Classes)
元字元是正則表示式規定的一個特殊程式碼
程式碼 | 說明 |
---|---|
. | 匹配除換行符以外的任意字元 |
\w |
匹配字母或數字或下劃線或漢字[A-Za-z0-9_]
|
\s |
匹配任意的空白符[\f\n\r\t\v\u00a0]
|
\d |
匹配數字[0-9]
|
\ | 用於轉義 |
邊界(Boundaries)
程式碼 | 說明 |
---|---|
\b |
匹配單詞的開始或結束 例如:/\bno/
匹配"at noon"
中的"no"
|
^ | 匹配字串的開始 |
$ | 匹配字串的結束 |
分組(Grouping)與反向引用(back references)
字元 | 含義 |
---|---|
(x)
|
捕獲括號。匹配 x 並且捕獲匹配項。/(foo)/
匹配且捕獲"foo bar."
中的"foo"
。被匹配的子字串可以通過元素[n]
中找到,或 RegExp 物件的屬性$n
中找到。會消耗效能,如果需要捕獲的陣列,可以使用非捕獲括號 |
\n
|
反向引用(back reference),指向正則表示式中第 n 個括號(從左開始數)中匹配的子字串。/apple(,)\sorange\1/
匹配"apple, orange, cherry, peach."
中的"apple,orange,"
(最後的,
號) |
(?:x)
|
非捕獲括號(non-capturing parentheses)。匹配 x 不會捕獲匹配項。匹配項不能夠從結果再次訪問。(推薦) |
重複
程式碼/語法 | 說明 |
---|---|
* | 重複零次或更多次 |
+ | 重複一次或更多次 |
? | 重複零次或一次 |
x*? x+? |
最小可能匹配,非貪婪模式。/".*?"/
匹配"foo" "bar"
中的"foo"
|
{n} | 重複n次 |
{n,m} | 重複n到m次 |
斷言(Assertions)
程式碼/語法 | 說明 |
---|---|
x(?=y) |
只有當 x 後面緊跟著 y 時,才匹配 x。/Jack(?=Sprat)/
只有在'Jack'
後面緊跟著'Sprat'
時才匹配 |
x(?!y) | 只有當 x 後面不是緊跟著 y 時,才匹配 x。 |
例項屬性
RegExp.prototype.global RegExp.prototype.ignoreCase RegExp.prototype.lastIndex RegExp.prototype.multiline RegExp.prototype.source RegExp.prototype.sticky
例項方法
-
RegExp.prototype.exec()
在目標字串中執行一次正則匹配操作。 -
RegExp.prototype.test()
測試當前正則是否能匹配目標字串。 -
String.prototype.match(pattern)
根據pattern
對字串進行正則匹配,返回匹配結果陣列,如匹配不到返回null
-
String.prototype.replace(pattern, replacement)
根據pattern
進行正則匹配,把匹配結果替換為replacement
, 返回一個新的字串
實戰
改變文字結構
var re = /(\w+)\s(\w+)/ // \w 匹配 [A-Za-z0-9_] // + 重複一次或更多次 // \s 匹配 空格符 // () 捕獲括號 var str = "John Smith" var newstr = str.replace(re, "$2, $1") // $1: 捕獲的值為 John // $2: 捕獲的值為 Smith console.log(newstr) //轉換後為 "Smith, John" 複製程式碼
訓練1: 請通過正則表示式,把() => 1+1
修改為function () { return 1+1 }
多行匹配
var s = "Please yes\nmake my day!"; s.match(/yes.*day/); // . 符號預設不匹配 \n // 可以改為 /yes.*day/s ,使用 s 識別符號來匹配多行 // 返回 null s.match(/yes[^]*day/); // ^ 匹配字串的開始 // [^]* 表示匹配任何字元,包括換行符 // 返回 yes\nmake my day 複製程式碼
訓練2: 請通過正則表示式,獲取到ab a\nb ab
中的a\nb
字串
使用帶有 ”sticky“ 標誌的正則表示式
var text = "First line\nsecond line"; var regex = /(\S+) line\n?/y; // () 捕獲括號 // \S+ 匹配一次或更多次非空白符 // \n? 匹配零到一次換行符 var match = regex.exec(text); console.log(match[1]);// 匹配到 First // 輸入 match[0] 返回的是 First line\n 所要進行匹配的文字 console.log(regex.lastIndex); // 匹配到位置 11 var match2 = regex.exec(text); console.log(match2[1]); // 匹配到 Second console.log(regex.lastIndex); // 匹配到位置 22 var match3 = regex.exec(text); console.log(match3 === null); // 返回 true, 沒有匹配到結果 複製程式碼
訓練3: 有文字"price: 10¥\nprice: 20¥\nprice: 30¥"
, 請獲取到 price 的值,儲存到陣列中,結果輸出為['10', '20', '30']
匹配連結
提取連結傳遞引數的值
// 提取 str 的 name 和 age var str = 'https://www.baidu.com?name=jawil&age=23' // 獲取name的值 var regName = /\?name=([^&]*)/ // \? 表示匹配 ? 字元,從該字元開始匹配 // name= 表示匹配 name= 的字串 // [^&]* 表示匹配字元直到遇到 & 符號為止 // ([^&]*) 表示捕獲匹配到的結果 // match 的值為 ["?name=jawil", "jawil"] var match = regName.exec(str) console.log(match[1]) // 返回 jawil 複製程式碼
訓練4: 請修改正則表示式,獲得到 age 的屬性值, 該正則表示式是怎麼樣的?
數字格式化
把 1234567890 格式化為 1,234,567,890
var str = '1234567890' var reg = /\B(?=(\d{3})+(?!\d))/g // \b 匹配單詞的開始或結束 // \B 匹配單詞的非開始和非結束的位置 //\Bon 匹配 ' on' 為 null,即開頭不能為 on //\Bon 匹配 ' onon' 為 on,匹配後一個 on // (\d{3}+) 捕獲1個或多個的3個連續數字 // (?!\d) 斷言: 表示3個數字不允許後面跟著數字 // (\d{3}+)+(?!\d) 表示匹配的後邊界跟著 3*n(n>=1)的數字. // (?=) 斷言: 表示後邊必須跟著的內容 //\B(?=(\d{3}+)+(?!\d)) 匹配非邊界後跟著的3個數字 // 執行過程 // 第一次匹配到為 ["", "890"] 沒有匹配到文字,但捕獲到3個數字 // 替換後為 1234567,890 // 第二次匹配到為 ["", "567"] // 替換後為 1234,567,890 // 第三次匹配到為 ["", "234"] // 替換後為 1,234,567,890 // 最後一次執行結果為 null 替換結束 var format = str.replace(reg, ',') console.log(format) // 1,234,567,890 複製程式碼
訓練5: 通過正則表示式把字串'255255255255' 格式化為 ip地址格式 '255.255.255.255'
訓練6: 編寫正則表示式測試傳入的ip地址是否正確,例如傳入1.1.1.1
返回 true, 傳入1.1.1.257
返回 false
虛擬 DOM 標籤解析
在Vue原始碼中構建虛擬DOM, 首先把 template 編譯成AST語法樹, 再轉換為 render 函式 最終返回一個VNode(VNode就是Vue的虛擬DOM節點)
而要編譯 template, 首先就是要對 template 進行解析。解析的過程中使用到了正則表示式, 比如解析下面程式碼
var dom = `<div :class="c" class="demo" v-if="isShow">{{item}}</div>` // 匹配變數名 const variable = /[a-zA-Z_][\w\-\.]*/ // 匹配標籤名 const tagName = /^<([a-zA-Z_][\w\-\.])*/ console.log(tagName.exec(dom)[1])// 返回 div // 匹配屬性 const attr = /\s*([^\s'"<>/=]+)(?:=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+))/g // \s*([^\s'"<>/=]+) 匹配屬性名 // (?:=) 匹配賦值號 = // (?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)) 匹配屬性值,包括 ""、 '' 和其它形式 var res = attr.exec(dom) console.log(res[1]) // 返回 :class console.log(res[2]) // 返回 "c" // 匹配 {{}} 裡的內容 const val = /{{(.*)}}/ console.log(val.exec(dom))// 返回 item 複製程式碼
答案
訓練1:
var str = '() => 1+1' var reg = /(\(\))\s(=>)\s([^]*)/ var str1 = str.replace(reg, 'function $1 { return $3 }') 複製程式碼
訓練2:
var s = "ab a\nb ab" s.match(/a\nb/) 複製程式碼
訓練3:
var str = 'price: 10¥\nprice: 20¥\nprice: 30¥' var reg = /\D*(\d+)/y var a = [] while((match = reg.exec(str)) !== null) { a.push(match[1]) } console.log(a) 複製程式碼
訓練4:
var str = 'https://www.baidu.com?name=jawil&age=23' var regAge = /\&age=([^&]*)/ var match = regAge.exec(str) 複製程式碼
訓練5:
var pattern = /\B(?=(\d{3})+(?!\d))/g var str = '255255255255' str.replace(pattern, '.') 複製程式碼
訓練6:
- 首先要匹配的是 0 ~ 255
-
匹配 50 ~ 55:
5[0-5]{1}
-
匹配 0 ~ 49:
[0-4]\d{1}
-
匹配 200 ~ 255:
2(5[0-5]{1}|[0-4]\d{1})
-
匹配 0 ~ 199:
[0-1]?\d{1,2}
-
完整匹配 0 ~ 255:
-
(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})
-
組合成檢測的ip 的正則表示式
var reg1 = /(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})/ // 把後邊3段檢測程式碼進行簡化後 var reg2 = /(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})(\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})){3}/ 複製程式碼