theme: channing-cyan
highlight: a11y-dark
本文正在参加「金石计划 . 瓜分6万现金大奖」
正则表达式 (Vue源码中的使用)🛫
前言
推荐先阅读
- 手摸手快速入门 正则表达式 (基础)🛫
- 手摸手快速入门 正则表达式 (方法)🛫
- 手摸手快速入门 正则表达式 (进阶)🛫
- 手摸手快速入门 正则表达式 (高级)🛫
- 手摸手快速入门 正则表达式 (实战案例)🛫
再阅读本文。
本文主要研究下 正则表达式,在 Vue 源码 中的使用
vue2源码
在 vue2
源码的 src\compiler\parser\html-parser.js
文件中
里面有大量的正则表达式,如下图
可以看到非常的长,不是我说,就前几行,如果没有相关的 正则表达式 的工具,我可能就被劝退了😭
这里先推荐一个 可视化正则表达式 工具,非常好用!
回归正题,那上面这么多 正则表达式 代表的什么含义呢?
-
它们主要是用来匹配我们的
html
模板 -
之后会将匹配到的内容,转化成
AST
语法树
可以看到这些是很重要的,
我们接下来分析理解一下里面的相关内容。
1. startTagOpen
它是 html
: 开始标签的 左边 部分
而生成这个 正则表达式 的代码有四行
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/
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}]*`
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
它的作用,或者说它能够匹配到的内容
-
标签开始符号
<
,当然后面会跟着相关的标签内容,下面会分析有哪些标签内容 -
但是注意 没有 标签结束符
>
1.1 ncname
在知道它匹配什么内容了之后,我们看一下 ncname
,
可以看到它是一个 字符串,里面有 unicodeRegExp.source
这就引出我们第一个知识点,正则的 RegExp.source
可以看下它的 MDN 介绍,我们知道它这个属性的作用是
-
获取正则表达式里面的内容
-
即获取到 两侧的斜杠
/.../
之间的内容
即,我们得到 ncname
这个字符串的值为
const ncname = '[a-zA-Z_][\\-\\.0-9_a-zA-Za-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]*'
转化为正则后
const newNcanme = new RegExp(ncname)
/[a-zA-Z_][\-\.0-9_a-zA-Za-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]*/
我们将这个正则表达式放到 可视化工具 中,得到下图
可以看到它能够匹配,开头为 a-z
、 A-Z
、_
中的其中一个,后面则是零次或多次
1.2 qnameCapture
知道 ncname
匹配的内容是什么之后,我们接下来我们看下 qnameCapture
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
'((?:[a-zA-Z_][\\-\\.0-9_a-zA-Za-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]*\\:)?[a-zA-Z_][\\-\\.0-9_a-zA-Za-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]*)'
转化为正则,并将其放入 可视化工具 中
const newQnameCapture =new RegExp(qnameCapture)
/((?:[a-zA-Z_][\-\.0-9_a-zA-Za-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]*\:)?[a-zA-Z_][\-\.0-9_a-zA-Za-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]*)/
从 代码 和 图,一起分析,可以知道
-
将它们整体作为了一个 组
-
又将前面第一部分作为 非捕获分组,虽然需要匹配内容,但是我们并不需要它的一个结果
(?:表达式)
-
在表达式后面,可以看到有一个
:
字符,说明,如果前面这个匹配, 那么后面必须跟上一个:
字符才算成功 -
在前面的分组后面紧跟 重复 字段
?
,说明它能够匹配 0次 或 1 次 -
最后面则就是
ncname
,上面已经分析过了。
1.3 startTagOpen
最后就是我们的 startTagOpen
这个其实很好理解,就是在前面加上了一个元字符 ^
和 <
代表的意思就是,必须是 <
开头
-
但是注意哦,并没有说结尾
-
结尾则是后面要讲的
startTagClose
来匹配的
这里我们依然进行一波 处理,看下结果
const startTagOpen = new RegExp(`^<${qnameCapture}`)
/^<((?:[a-zA-Z_][\-\.0-9_a-zA-Za-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]*\:)?[a-zA-Z_][\-\.0-9_a-zA-Za-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]*)/
1.4 总结
我们知道了 startTagOpen
的一个作用
就是用来以匹配 <
开头,后面跟上标签,标签开头必须是 a-z
、A-Z
、_
中的一个
例如:<div
、<xxx:yyy
值得注意:它是没有结束的右尖括号 >
的
2. startTagClose
它是 html
: 开始标签的 右边 部分
先看下正则表达式
const startTagClose = /^\s*(\/?)>/
这个主要是匹配 标签结束符 >
,当然还包括一些其它内容
-
前面可能有空格,
-
可能有
/
字符
例如 <div>
或者 单标签 <img />
的 右边部分 >
和 />
下面是它的 正则表达式 图,可以看到很简单
3. endTag
它是 html
: 整个 结束标签
例如 </div>
、 </xxx:yyy>
这里看下它的 正则表达式 以及 图
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
/^<\/((?:[a-zA-Z_][\-\.0-9_a-zA-Za-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]*\:)?[a-zA-Z_][\-\.0-9_a-zA-Za-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]*)[^>]*>/
可以看到 结束标签 的 开头 必须是 </
,结尾 必须是 >
中间的内容就跟上面分析的一样
但是结尾前边有一个 非 >
的区间,就有点疑问了!
-
虽然这个含义很好理解,但是为什么加这个呢?
-
仅仅只是为了说明,前面只要不是
>
都行?但是前面已经有ncanme
了。 -
所以这个字符的真正含义,有没有大佬出来解答下😁
4. attribute
看这个单词的含义,我们都知道是 属性 的意思
因为后面还有 dynamicArgAttribute
所以我们叫 attribute
,为 普通的 html 属性
那我们来分析下这个 正则表达式 的含义吧
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
害,光看这么长,都有点脑瓜子嗡嗡的😵💫
顺便放到工具中,得到下图
4.1 正则分析 – 1
可以看到一上去,前面可以匹配一些空格 \s*
之后遇到 第一个分组
-
里面是一个 区间,因为开头是
^
,说明是一个取反操作 -
里面不能是 (空格、
"
、'
、<
、>
、/
、=
) -
并且后面有一个 重复 字符
+
,说明必须匹配一次以上
往后看,是 第二个分组
-
不过这个分组是一个 非捕获分组
(?:表达式)
-
而且这个它后面,紧跟一个 重复 字符
?
,说明匹配0
次或1
次
到这里我们先暂停下,
通过上面的分析,我们可以大概知道,它能够匹配的属性是什么
它能够匹配 单个属性 ,例如:
<button disabled>
这里面的disabled
属性
4.2 正则分析 – 2
我们接着分析 非捕获分组 这一大段里面的内容 (红框部分)
/(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))/
我们可以看到有个小的分组,里面是 =
字符,左右两边都是 \s*
说明
=
左右两边 可以有 空格 出现,但是平时我们都是紧贴着的是因为
Eslint
给我们提示了,而vue
本身是支持=
左右两边 有空格的。
我们继续分析
可以看到又是一个 非捕获分组,里面的内容通过 或运算符 |
连接
-
"([^"]*)"+
说明了 可以是 双引号 -
'([^']*)'+
说明了 可以使 单引号 -
最后则表示,匹配非 空格、
"
、'
、=
、<
、>
、`
通过第二段分析,我们直接说一下它能够匹配到的内容是什么
例如:
-
class = "aaa"
、class="aaa"
-
style = 'color:red;'
、style='color:red;'
-
第三个,其实没找到很好的例子,希望有大佬来解惑🎁
4.3 总结
总结下,上面这两点,其实它这个匹配的内容是
-
无
=
号- 例如
<button disabled
中的disabled
这类
- 例如
-
有
=
号-
例如
<button style="" class=''
中的"
或者'
这类 -
当然,
=
号 的左右两边都可以有空格
-
5.dynamicArgAttribute
上面我们说了 普通的 html
属性
下面说一下 vue
的 动态属性
用过 vue
都知道 动态属性,就是在普通属性前面加一些 符号
,例如
-
v-xxx=
-
:xxx=
-
@xxx=
-
#xxx=
这个我也是看完源码后,才知道,还有这么个符号(表示根本没用过…)- 而它呢,是
v-slot
的简写
- 而它呢,是
当然,我们这里还是要分析下它的 正则表达式
/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
并且得到如下图:
这里我将它与 普通 html
属性 的区别,用红色的框 框起来了
5.1 正则分析 – 1
(?:v-[\w-]+:|@|:|#)
是一个 非捕获分组,而它匹配到的内容为
-
v-
后面紧跟上 区间,区间 里面包含\w
和-
-
\w
是能够匹配 字母、数字、下划线( _ ) 、中文 -
而 区间 后面,有个重复
+
,说明至少匹配区间里面的内容一次 -
最后跟上一个
:
字符
-
-
@
-
:
-
#
5.2 正则分析 – 2
\[[^=]+\]
这个很简单了
-
首先是 转义字符
\
帮我们将[
进行了转义 -
之后是一个 区间,但是第一个字符是
^
,说明进行了 取反 的操作-
也就是 除了
=
外的 任意字符 -
后面有个 重复字符
+
,至少匹配一次
-
-
最后是 转义后的
]
5.3 总结
通过上面的分析,我们能够得到这个 正则表达式 可以匹配的内容是
下面是大概的例子
-
<div v-bind:[disabled]="true"
、简写:[disabled]="true"
-
<div v-on:[click]="xxxFunc"
、 简写@[click]="xxxFunc"
-
<div v-slot:[xxx]="yyy"
、 简写#[xxx]="xxxFunc"
可以看到,匹配的内容,与我们平时写法有区别的。
6. doctype
其实这个大家熟悉也陌生
这个是我们 html
页面中,最上面的声明部分,
它的作用是告知 web
浏览器页面使用 哪种 html
版本
目前我们经常使用的是:
<!DOCTYPE html>
其它的情况,其实也没怎么见过
接下来看下正则吧
const doctype = /^<!DOCTYPE [^>]+>/i
-
首先是 元字符 的
^
开头,后面必须是<!DOCTYPE
-
后面
[^>]+
, 因为 区间 的开头是^
,说明进行了取反操作-
也就是能够匹配处理
>
以外,其它任意字符 -
最后是 重复 的
+
,至少匹配一次
-
-
然后是
>
结尾 -
当然,我们看到后面还有个 修饰符
i
,不区分大小写
它这个意思就是能够 匹配 我们 html
页面中
-
最开头的
<!DOCTYPE xxxxxx yyyy>
这个声明 -
或者小写的
<!doctype xxxxxx yyyy>
都可以
7. comment
const comment = /^<!\--/
这个是匹配我们的 html
中,注释的 开头,也就是 <!--
- 元字符 的
^
开头,后面紧跟<!--
这个较容易理解,我们直接下一个
8. conditionalComment
首先这个名字是,条件注释 的意思
其次,这个使用的场景,目前搜到的,是为了判断 IE
浏览器版本时用的
- 平时很少见
看下 正则表达式
const conditionalComment = /^<!\[/
与上面的 comment
很相似,就不再做分析
说一下,它匹配到的内容: <![
9.encodedAttrWithNewLines
encodedAttr
和 encodedAttrWithNewLines
const encodedAttr = /&(?:lt|gt|quot|amp|#39);/g
const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#39|#10|#9);/g
可以看到这两个 正则表达式 实在是太相似了,所以放到一起讲解下
其实它的匹配内容很简单
但是呢它的作用,我大概感觉可能跟 编解码 有关,具体的作用则需要通读一下源码才能了解。
这里就靠评论区大佬们,来解答这两个 正则表达式 的真正作用了🎁
我们这里还是分析下 正则表达式:
-
首先是一个 & 符号,后面则是一个 非捕获分组,
-
里面要么是
lt
、gt
、quot
、amp
、#39
、#10
、#9
中的任意一个 -
最后是一个
;
分号 结尾
可以看到它与 decodingMap
里面的内容很相关,所以我分析是跟 编解码
有关。
总结
这是最后一期 正则表达式
通过上面一系列的 正则表达式 的学习,以及 实战案例,还有 分析源码 中的使用
相信小伙伴们在 日常项目 ,或者 看源码时 ,不会再被 正则表达式 劝退了
愿世间再无 正则分析师
暂无评论内容