寫作本文的起源在於,在分析 Vue 源碼中 parseHTML
方法時,發現網上對其中正則的解析文章較少,找到的幾篇文章也有些語焉不詳。於是靜下心逐個表達式分析了其中的正則,以備查看。
常見正則規則可參見附錄 1,Vue parseHTML
正則所用規則均可在其中找到定義。
Vue parseHTML
中所用的所有正則如下:
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}]*`
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
const doctype = /^<!DOCTYPE [^>]+>/i
const comment = /^<!\--/
const conditionalComment = /^<!\[/
接下來一個個通過拆解表達式,來分析上述正則規則。
attribute
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
分析其結構:
-
^\s*
匹配 0 至多個以空白字符開頭的字符串空白字符的部分 -
捕獲組:
([^\s"'<>\/=]+)
匹配並捕獲 1 至多次除空白字符
"
'
<
>
/
=
以外的所有字符 -
非捕獲組:(?:\s(=)\s(?:"(["]*)"+|'([']*)'+|([^\s"'=<>`]+)))?
\s*
匹配 0 至多個空白字符- 捕獲組:
(=)
匹配並捕獲=
\s*
匹配 0 至多個空白字符- 非捕獲組:
(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>\
]+))`"([^"]*)"+
"
匹配"
([^"]*)
匹配並捕獲 0 至多個除"
外的字符"+
匹配 1 至多次"
'([^']*)'+
'
匹配'
([^']*)
匹配並捕獲 0至多個除'
外的字符'+
匹配 1 至多次'
- ([^\s"'=<>`]+) 匹配並捕獲 1 至多次除
空白字符
"
'
=
<
>
` 外的字符
?
匹配 3 中非捕獲組 0 次或 1 次
小結
attribute 表達式匹配的是:
-
以 0 至多個空白字符開頭;
-
緊接着 1 至多個除
空白字符
"
'
<
>
/
=
以外的字符; -
緊接着 0 至多個空白字符;
-
緊接着
=
; -
緊接着 0 至多個空白字符;
-
緊接着匹配 0 次或 1 次:
(1)
"
+ 0 至多個除"
外的字符 +"
;(2) 或
'
+ 0 至多個除'
外的字符 +'
;(3) 或 1 至多次除
空白字符
"
'
=
<
>
` 外的字符
例如:
<div id="mydiv" class="myClass" style="color: #ff0000" >
在 Vue 的 parseHTML 時,就能將 id="mydiv"
、class="myClass"
、style="color: #ff0000"
提取出來。
dynamicArgAttribute
const dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
分析其結構:
-
^\s*
匹配以 0 至多個空白字符開頭 -
捕獲組:
((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)
-
非捕獲組:
(?:v-[\w-]+:|@|:|#)
匹配:(1)
v-
+ 1 次或多次包括下划線在內的任意單詞字符 +:
(2)或
@
(3)或
:
(4)或
#
-
\[[^=]+\]
匹配 以[
+ 1 次或多次除=
外的所有字符 +]
-
[^\s"'<>\/=]*
匹配 0 次或多次除空白字符
、"
、'
、<
、>
、/
、=
以外的字符
- 非捕獲組:(?:\s(=)\s(?:"(["]*)"+|'([']*)'+|([^\s"'=<>`]+)))?
已在 attribute
章節分析過。
小結
dynamicArgAttribute 用於匹配:
-
以 0 至多個空白字符開頭
-
緊接着:
(1)
v-
+ 1 次或多次包括下划線在內的任意單詞字符+ :
;(2)或
@
(3)或
:
(4)或
#
-
緊接着以
[
+ 1 次或多次除=
外的所有字符 +]
-
匹配 0 次或多次除
空白字符
、"
、'
、<
、>
、/
、=
以外的字符 -
緊接着 0 至多個空白字符;
-
緊接着
=
; -
緊接着 0 至多個空白字符;
-
緊接着匹配 0 次或 1 次:
(1)
"
+ 0 至多個除"
外的字符 +"
;(2) 或
'
+ 0 至多個除'
外的字符 +'
;(3) 或 1 至多次除
空白字符
"
'
=
<
>
` 外的字符
例如:
<a v-bind:[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
在 Vue 的 parseHTML 時,就能將 v-bind:[attributeName]="url"
這種動態參數提取出來。
ncname
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}]*`
首先看 unicodeRegExp
const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/
定義了一系列合法字符,通過 Unicode 字符集范圍匹配。
unicodeRegExp.source
用於拿到正則表達式 unicodeRegExp
的字符串。
ncname
即是一系列合法字符的集合。
qnameCapture
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
表示匹配 xxx:xxx
或 xxx
模式的字符。
startTagOpen
const startTagOpen = new RegExp(`^<${qnameCapture}`)
startTagOpen
可匹配標簽開始部分,即:<xxx:xxx
或 <xxx
的模式。
<xxx:xxx
代表的是帶命名空間的 html 標簽,文可參見這里,這種類型的標簽主要作用是可以指定標簽的命名空間,避免沖突。Vue 對這類的標簽也做了解析。
如:<div
或 <math:div
startTagClose
const startTagClose = /^\s*(\/?)>/
^\s*(\/?)>
匹配以 0 至多個空白字符開頭,接 0 或 1 個 /
,緊接 >
的字符串。
如: />
endTag
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
匹配以 </
開頭,后接合法字符,后接 0 至多個除 >
以外的任何字符,最后接 >
。
如:</div>
doctype
const doctype = /^<!DOCTYPE [^>]+>/i
匹配以 <!DOCTYPE
開頭,后接 1 至多次除 >
外的所有字符,后接 >
。注意該匹配模式不區分大小寫。
如:<!DOCTYPE html>
comment
const comment = /^<!\--/
匹配以 <!--
開頭的字符串。
如:<!--STATUS OK-->
conditionalComment
const conditionalComment = /^<!\[/
匹配以 <![
開頭的字符串。條件注釋主要用於做瀏覽器兼容等,文可參見這里
總結
本文以 Vue 源碼中 parseHTML
方法為例,分析了其中定義的正則表達式,將常見的正則規則做了梳理,同時可以備查 parseHTML 方法的正則規則,方便后續繼續分析該方法。
附錄 1 常見正則規則
正則里的特殊字符
* ? + . [ ] ( ) { } | ^ $,共 13 個。
*
表示匹配前面字符(或括號括起來的表達式,或方括號括起來的字符集)0 次或 多 次;?
(1)? 表示匹配前面字符(或括號括起來的表達式,或方括號括起來的字符集)0 次或 1 次;(2)? 緊跟在其它任何一個限制符(如 * + 等)后時,匹配模式為非貪婪,盡可能少地匹配;+
表示匹配前面字符(或括號括起來的表達式,或方括號括起來的字符集)1 次或 多 次;.
表示匹配除換行符以外的任意字符 1 次;[]
字符集,表示匹配方括號內字符中的其中一個,其中的特殊字符會被當做普通字符處理;()
捕獲組,表示匹配其中的子表達式;{n,m}
匹配前面表達式最少 n 次,最多 m 次|
或,常用在捕獲組中;^
匹配字符串的開始位置$
匹配字符串的結束位置
表達式 ()
中常見寫法
(pattern)
匹配 pattern 並獲取該匹配;(?:pattern)
匹配 pattern 但不獲取該匹配;pattern1(?=pattern2)
匹配 pattern1 后面跟 pattern2 的字符串,不獲取該匹配pattern1(?!pattern2)
匹配 pattern1 后面不跟 pattern2 的字符串,不獲取該匹配(?<=pattern2)pattern1
匹配 pattern1 前面為 pattern2 的字符串,不獲取該匹配(?<!pattern2)pattern1
匹配 pattern1 前面不為 pattern2 的字符串,不獲取該匹配
字符集 []
常見寫法
[x|y]
匹配 x 或 y,可為字符串[xyz]
匹配 x 或 y 或 z 的字符[^xyz]
匹配 非 x 或 y 或 z 的字符[a-z]
匹配 a 到 z 的任意小寫字符
常見特殊字符
\b
匹配單詞邊界\d
匹配一個數字字符\n
匹配一個換行符\r
匹配一個回車符\t
匹配制表符\s
匹配任何空白字符\w
匹配包括下划線的任何單詞字符