从 Vue parseHTML 来学习正则表达式
写作本文的起源在于,在分析 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
匹配包括下划线的任何单词字符
推荐阅读
-
通过vue-cli来学习修改Webpack多环境配置和发布问题
-
从探索世界的本源来学习python的组成
-
详解从Node.js的child_process模块来学习父子进程之间的通信
-
从 Vue parseHTML 来学习正则表达式
-
学习node从0做一个爬虫,有朋友留言说想学习一下VUE,我整理了之前自学时候的学习资源分享给大家。
-
【vue】vue项目中使用antd UI框架,从创建新项目开始一步一步来探索吧
-
Vue.js 2.0学习教程之从基础到组件详解
-
请查收这份学习笔记!我从Vue源码中学到的5个JavaScript技巧
-
详解从Node.js的child_process模块来学习父子进程之间的通信的示例代码
-
通过vue-cli来学习修改Webpack多环境配置和发布问题