手摸手快速入门 正则表达式 (Vue源码中的使用)🛫


theme: channing-cyan
highlight: a11y-dark

本文正在参加「金石计划 . 瓜分6万现金大奖」

正则表达式 (Vue源码中的使用)🛫

前言

推荐先阅读

再阅读本文。

本文主要研究下 正则表达式,在 Vue 源码 中的使用

vue2源码

vue2 源码的 src\compiler\parser\html-parser.js 文件中

里面有大量的正则表达式,如下图

image.png

可以看到非常的长,不是我说,就前几行,如果没有相关的 正则表达式 的工具,我可能就被劝退了😭

这里先推荐一个 可视化正则表达式 工具,非常好用!

回归正题,那上面这么多 正则表达式 代表的什么含义呢?

  • 它们主要是用来匹配我们的 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]*/

我们将这个正则表达式放到 可视化工具 中,得到下图

image.png

可以看到它能够匹配,开头为 a-zA-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]*)/

image.png

从 代码 和 图,一起分析,可以知道

  • 将它们整体作为了一个

  • 又将前面第一部分作为 非捕获分组,虽然需要匹配内容,但是我们并不需要它的一个结果 (?:表达式)

  • 在表达式后面,可以看到有一个 : 字符,说明,如果前面这个匹配, 那么后面必须跟上一个 : 字符才算成功

  • 在前面的分组后面紧跟 重复 字段 ?,说明它能够匹配 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]*)/

image.png

1.4 总结

我们知道了 startTagOpen 的一个作用

就是用来以匹配 < 开头,后面跟上标签,标签开头必须是 a-zA-Z_ 中的一个

例如:<div<xxx:yyy

值得注意:它是没有结束的右尖括号 >

2. startTagClose

它是 html开始标签右边 部分

先看下正则表达式

const startTagClose = /^\s*(\/?)>/

这个主要是匹配 标签结束符 >,当然还包括一些其它内容

  • 前面可能有空格,

  • 可能有 / 字符

例如 <div> 或者 单标签 <img />右边部分 >/>

下面是它的 正则表达式 图,可以看到很简单

image.png

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]*)[^>]*>/

image.png

可以看到 结束标签 的 开头 必须是 </,结尾 必须是 >

中间的内容就跟上面分析的一样

但是结尾前边有一个 非 > 的区间,就有点疑问了!

  • 虽然这个含义很好理解,但是为什么加这个呢?

  • 仅仅只是为了说明,前面只要不是 > 都行?但是前面已经有 ncanme 了。

  • 所以这个字符的真正含义,有没有大佬出来解答下😁

4. attribute

看这个单词的含义,我们都知道是 属性 的意思

因为后面还有 dynamicArgAttribute

所以我们叫 attribute,为 普通的 html 属性

那我们来分析下这个 正则表达式 的含义吧

const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/

害,光看这么长,都有点脑瓜子嗡嗡的😵‍💫

顺便放到工具中,得到下图

image.png

4.1 正则分析 – 1

可以看到一上去,前面可以匹配一些空格 \s*

之后遇到 第一个分组

  • 里面是一个 区间,因为开头是 ^,说明是一个取反操作

  • 里面不能是 (空格、"'<>/=

  • 并且后面有一个 重复 字符 +,说明必须匹配一次以上

往后看,是 第二个分组

  • 不过这个分组是一个 非捕获分组 (?:表达式)

  • 而且这个它后面,紧跟一个 重复 字符 ,说明匹配 0 次或 1

到这里我们先暂停下,

通过上面的分析,我们可以大概知道,它能够匹配的属性是什么

它能够匹配 单个属性 ,例如:

  • <button disabled> 这里面的 disabled 属性

4.2 正则分析 – 2

我们接着分析 非捕获分组 这一大段里面的内容 (红框部分)

/(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))/

image.png

我们可以看到有个小的分组,里面是 = 字符,左右两边都是 \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"'=<>`]+)))?/

并且得到如下图:

image.png

这里我将它与 普通 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

image.png

  • 首先是 元字符^ 开头,后面必须是 <!DOCTYPE

  • 后面 [^>]+, 因为 区间 的开头是 ^,说明进行了取反操作

    • 也就是能够匹配处理 > 以外,其它任意字符

    • 最后是 重复+,至少匹配一次

  • 然后是 > 结尾

  • 当然,我们看到后面还有个 修饰符 i,不区分大小写

它这个意思就是能够 匹配 我们 html 页面中

  • 最开头的 <!DOCTYPE xxxxxx yyyy> 这个声明

  • 或者小写的 <!doctype xxxxxx yyyy> 都可以

7. comment

const comment = /^<!\--/

这个是匹配我们的 html 中,注释的 开头,也就是 <!--

  • 元字符^ 开头,后面紧跟 <!--

这个较容易理解,我们直接下一个

8. conditionalComment

首先这个名字是,条件注释 的意思

其次,这个使用的场景,目前搜到的,是为了判断 IE 浏览器版本时用的

  • 平时很少见

看下 正则表达式

const conditionalComment = /^<!\[/

与上面的 comment 很相似,就不再做分析

说一下,它匹配到的内容: <![

9.encodedAttrWithNewLines

encodedAttrencodedAttrWithNewLines

const encodedAttr = /&(?:lt|gt|quot|amp|#39);/g
const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#39|#10|#9);/g

可以看到这两个 正则表达式 实在是太相似了,所以放到一起讲解下

其实它的匹配内容很简单

但是呢它的作用,我大概感觉可能跟 编解码 有关,具体的作用则需要通读一下源码才能了解。

这里就靠评论区大佬们,来解答这两个 正则表达式 的真正作用了🎁

我们这里还是分析下 正则表达式

image.png

  • 首先是一个 & 符号,后面则是一个 非捕获分组

  • 里面要么是 ltgtquotamp#39#10#9 中的任意一个

  • 最后是一个 ; 分号 结尾

可以看到它与 decodingMap 里面的内容很相关,所以我分析是跟 编解码 有关。

总结

这是最后一期 正则表达式

通过上面一系列的 正则表达式 的学习,以及 实战案例,还有 分析源码 中的使用

相信小伙伴们在 日常项目 ,或者 看源码时 ,不会再被 正则表达式 劝退了

愿世间再无 正则分析师

Snipaste_2022-12-08_14-51-43.png
Snipaste_2022-12-08_14-52-26.png

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容