最近學習了 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
零寬斷言
如果不想匹配符號,只匹配一個位置,就要用到"零寬斷言"。用於幫我們匹配文字,而它自身不會被匹配到內容中去。
var str = 'ttabttdef'
var reg = /tt(?=ab)/
reg.exec(str)
// 匹配到的是 tt, 只有後面跟的是 ab 的才匹配
var reg1 = /tt(?=ab)ab/
reg1.exec(str)
// 匹配到的是 ttab, 斷言只匹配位置, 所以斷言匹配的這些字元會接下去進行匹配。
// 而不會因為被斷言匹配過了就跳過這些字元。
複製程式碼
訓練7: 使用斷言匹配出字串 <div>red blue green</div>
中的字串 red blue green
。
訓練8: 使用斷言,匹配出<xxx>任意字串</xxx>
中的 任意字串
, 其中 xxx 表示標籤名, 比如匹配出<div>adsasdfa<br>asdfsd</div>
中的 adsasdfa<br>asdfsd
虛擬 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}/
複製程式碼
訓練7:
var str="<div>red blue green</div>"
var reg = /<div>(.*)(?=<)/
reg.exec(str)[1] // red blue green
複製程式碼
訓練8:
var str = '<div>asdas<br>dfadfsdasdf</div>'
var reg = /^<(\w+)>(.*)(?=<\/\1>)/
reg3.exec(str)[2] // asdas<br>dfadfsdasdf
複製程式碼