学习vue源码(7)手写解析器(中)

}

}

console.log(parseStartTagEnd(‘>’)) // {unarySlash: “”}

console.log(parseStartTagEnd(‘/>

’)) // {unarySlash: “/”}

这段代码可以正确解析出开始标签是否是自闭合标签。

从代码中打印出来的结果可以看到,自闭合标签解析后的unarySlash属性为/,而非自闭合标签为空字符串。

3. 实现源码

前面解析开始标签时,我们将其拆解成了三个部分,分别是标签名、属性和结尾。我相信你已经对开始标签的解析有了一个清晰的认识,接下来看一下Vue.js中真实的代码是什么样的:

const ncname = ‘[a-zA-Z_][\w\-\.]*’

const qnameCapture = ((?:${ncname}\\:)?${ncname})

const startTagOpen = new RegExp(^<${qnameCapture})

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

function advance (n) {

html = html.substring(n)

}

function parseStartTag () {

// 解析标签名,判断模板是否符合开始标签的特征

const start = html.match(startTagOpen)

if (start) {

const match = {

tagName: start[1],

attrs: []

}

advance(start[0].length)

// 解析标签属性

let end, attr

while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {

advance(attr[0].length)

match.attrs.push(attr)

}

// 判断是否是自闭合标签

if (end) {

match.unarySlash = end[1]

advance(end[0].length)

return match

}

}

}

上面的代码是Vue.js中解析开始标签的源码,这段代码中的html变量是HTML模板。

调用parseStartTag就可以将剩余模板开始部分的开始标签解析出来。如果剩余HTML模板的开始部分不符合开始标签的正则表达式规则,那么调用parseStartTag就会返回undefined。因此,判断剩余模板是否符合开始标签的规则,只需要调用parseStartTag即可。如果调用它后得到了解析结果,那么说明剩余模板的开始部分符合开始标签的规则,此时将解析出来的结果取出来并调用钩子函数start即可:

// 开始标签

const startTagMatch = parseStartTag()

if (startTagMatch) {

handleStartTag(startTagMatch)

continue

}

前面我们说过,所有解析操作都运行在循环中,所以continue的意思是这一轮的解析工作已经完成,可以进行下一轮解析工作。

从代码中可以看出,如果调用parseStartTag之后有返回值,那么会进行开始标签的处理,其处理逻辑主要在handleStartTag中。这个函数的主要目的就是将tagName、attrs和unary等数据取出来,然后调用钩子函数将这些数据放到参数中。

3 截取结束标签

结束标签的截取要比开始标签简单得多,因为它不需要解析什么,只需要分辨出当前是否已经截取到结束标签,如果是,那么触发钩子函数就可以了。

那么,如何分辨模板已经截取到结束标签了呢?其道理其实和开始标签的截取相同。

如果HTML模板的第一个字符不是<,那么一定不是结束标签。只有HTML模板的第一个字符是<时,我们才需要进一步确认它到底是不是结束标签。

进一步确认时,我们只需要判断剩余HTML模板的开始位置是否符合正则表达式中定义的规则即可:

const ncname = ‘[a-zA-Z_][\w\-\.]*’

const qnameCapture = ((?:${ncname}\\:)?${ncname})

const endTag = new RegExp(^<\\/${qnameCapture}[^>]*>)

const endTagMatch = ‘’.match(endTag)

const endTagMatch2 = ‘

’.match(endTag)

console.log(endTagMatch) // [“”, “div”, index: 0, input: “”]

console.log(endTagMatch2) // null

上面代码可以分辨出剩余模板是否是结束标签。当分辨出结束标签后,需要做两件事,一件事是截取模板,另一件事是触发钩子函数。而Vue.js中相关源码被精简后如下:

const endTagMatch = html.match(endTag)

if (endTagMatch) {

html = html.substring(endTagMatch[0].length)

options.end(endTagMatch[1])

continue

}

可以看出,先对模板进行截取,然后触发钩子函数。

4 截取注释

分辨模板是否已经截取到注释的原理与开始标签和结束标签相同,先判断剩余HTML模板的第一个字符是不是<,如果是,再用正则表达式来进一步匹配:

const comment = /^<!–/

if (comment.test(html)) {

const commentEnd = html.indexOf(‘–>’)

if (commentEnd >= 0) {

if (options.shouldKeepComment) {

options.comment(html.substring(4, commentEnd))

}

html = html.substring(commentEnd + 3)

continue

}

}

在上面的代码中,我们使用正则表达式来判断剩余的模板是否符合注释的规则,如果符合,就将这段注释文本截取出来。

这里有一个有意思的地方,那就是注释的钩子函数可以通过选项来配置,只有options.shouldKeepComment为真时,才会触发钩子函数,否则只截取模板,不触发钩子函数。

5 截取条件注释

条件注释不需要触发钩子函数,我们只需要把它截取掉就行了。

截取条件注释的原理与截取注释非常相似,如果模板的第一个字符是<,并且符合我们事先用正则表达式定义好的规则,就说明需要进行条件注释的截取操作。

在下面的代码中,我们通过indexOf找到条件注释结束位置的下标,然后将结束位置前的字符都截取掉:

const conditionalComment = /^<![/

if (conditionalComment.test(html)) {

const conditionalEnd = html.indexOf(‘]>’)

if (conditionalEnd >= 0) {

html = html.substring(conditionalEnd + 2)

continue

}

}

我们来举个例子:

const conditionalComment = /^<![/

let html = ‘<![if !IE]><![endif]>’

if (conditionalComment.test(html)) {

const conditionalEnd = html.indexOf(‘]>’)

if (conditionalEnd >= 0) {

html = html.substring(conditionalEnd + 2)

}

}

console.log(html) // ‘<![endif]>’

从打印结果中可以看到,HTML中的条件注释部分截取掉了。

通过这个逻辑可以发现,在Vue.js中条件注释其实没有用,写了也会被截取掉,通俗一点说就是写了也白写。

6 截取DOCTYPE

DOCTYPE与条件注释相同,都是不需要触发钩子函数的,只需要将匹配到的这一段字符截取掉即可。下面的代码将DOCTYPE这段字符匹配出来后,根据它的length属性来决定要截取多长的字符串:

const doctype = /^]+>/i

const doctypeMatch = html.match(doctype)

if (doctypeMatch) {

html = html.substring(doctypeMatch[0].length)

continue

}

示例如下:

const doctype = /^]+>/i

let html = ‘’

const doctypeMatch = html.match(doctype)

if (doctypeMatch) {

html = html.substring(doctypeMatch[0].length)

}

console.log(html) // ‘’

从打印结果可以看到,HTML中的DOCTYPE被成功截取掉了。

7 截取文本

若想分辨在本轮循环中HTML模板是否已经截取到文本,其实很简单,我们甚至不需要使用正则表达式。

在前面的其他标签类型中,我们都会判断剩余HTML模板的第一个字符是否是<,如果是,再进一步确认到底是哪种类型。这是因为以<开头的标签类型太多了,如开始标签、结束标签和注释等。然而文本只有一种,如果HTML模板的第一个字符不是<,那么它一定是文本了。

例如:

我是文本

上面这段HTML模板并不是以<开头的,所以可以断定它是以文本开头的。

那么,如何从模板中将文本解析出来呢?我们只需要找到下一个<在什么位置,这之前的所有字符都属于文本,如图4所示。

图4 尖括号前面的字符都属于文本

在代码中可以这样实现:

while (html) {

let text

let textEnd = html.indexOf(‘<’)

// 截取文本

if (textEnd >= 0) {

text = html.substring(0, textEnd)

html = html.substring(textEnd)

}

// 如果模板中找不到<,就说明整个模板都是文本

if (textEnd < 0) {

text = html

html = ‘’

}

// 触发钩子函数

if (options.chars && text) {

options.chars(text)

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

结尾

学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。

资料领取方式:戳这里免费获取

,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

[外链图片转存中…(img-nsRUuE44-1713778293424)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-mzAVO4Et-1713778293425)]

结尾

学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。

资料领取方式:戳这里免费获取

html5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值