模版编译原理
在前端开发中,我们通过写HTML代码来实现页面的结构,我们称这些HTML代码为模板。在vue中,模板先通过模板编译转换成渲染函数,当内部状态发生变化的时候,Vue.js会结合响应式系统,找出最小数量的组件进行重新渲染以进行最少量的DOM操作。
Vue中创建HTML除了使用模版,也可以手动写渲染函数来创建HTML,还可以使用JSX创建HTML。
流程
从这张图中,我们可以看到Vue的模版编译是在mount的过程中进行的,在mount的时候执行了compile这个方法来将template里的内容转换成真正的HTML代码,接下来就到了监听绑定的数据重新生成vnode,通过新旧vnode比对更新用户界面。
compile方法分为三个步骤:
- parse函数解析template
- optimize函数优化静态内容
- generate函数创建render函数字符串
也就是说,编译原理的整体逻辑分为下面三个部分。
- 将 模板字符串 转换成 element AST(解析器)
- 对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
- 使用 AST 生成 render 函数代码字符串(代码生成器)
解析器
作用:通过 模版 得到 AST(抽象语法树)
例如:
<div>
<p>{{name}}</p>
</div>
上面的代码转换成AST后的样子如下:
{
tag: "div"
type: 1,
staticRoot: false,
static: false,
plain: true,
parent: undefined,
attrsList: [],
attrsMap: {},
children: [
{
tag: "p"
type: 1,
staticRoot: false,
static: false,
plain: true,
parent: {tag: "div", ...},
attrsList: [],
attrsMap: {},
children: [{
type: 2,
text: "{{name}}",
static: false,
expression: "_s(name)"
}]
}
]
}
它在被HTML解析器解析的过程如下。
第一轮循环时,截取出一段字符串
`
<p>{{name}}</p>
</div>`
第二轮循环时,截取出一段字符串:
`
`
并且触发钩子函数chars,截取后的结果为:
`<p>{{name}}</p>
</div>`
第三轮循环时,截取出一段字符串
,并且触发钩子函数start,截取后的结果为:
`{{name}}</p>
</div>`
第四轮循环时,截取出一段字符串{{name}},并且触发钩子函数chars,截取后的结果为:
`</p>
</div>`
第五轮循环时,截取出一段字符串
,并且触发钩子函数end,截取后的结果为:`
</div>`
第六轮循环时,截取出一段字符串:
`
`
并且触发钩子函数chars,截取后的结果为:
`</div>`
第七轮循环时,截取出一段字符串
``
解析完毕。
HTML解析器内部原理:一小段一小段截取模版字符串,根据每次截取的一小段字符串触发不同的钩子函数,直到模版字符串截空通知运行。
使用栈维护DOM层级
构建AST层级关系只需要维护一个栈即可,这个层级关系也可以理解为DOM的深度。
使用栈结构还有另外一个作用:检测出HTML标签是否正确闭合。
优化器
作用:在AST中找出静态子树并打上标记。
好处:
- 重新渲染时,不需要为静态子树创建新节点
- 在虚拟DOM中打补丁的过程可以被跳过
内部实现的两个步骤:
- 在AST中找出所有静态节点并打上标记
- 在AST中找出所有静态根节点并打上标记
通过递归的方式从上向下标记静态节点。标记完静态节点之后需要标记静态根节点,使用递归的方式从上向下寻找,找到的第一个静态节点就是静态根节点,同时不会再向下继续寻找。
静态节点是永远不会发生变化的DOM节点,这个节点不需要重新渲染,所以标记起来不像普通节点一样创建更新它而直接克隆它,可以避免一些无用功来提升性能。其子节点必须是静态节点。
两种特殊情况:
- 一个静态根节点的子节点只有一个文本节点
- 找到的静态根节点是一个没有子节点的静态节点
这两种情况不会将这个节点标记为静态根节点,因为优化成本大于收益。
代码生成器
作用:使用 AST 生成 render 函数代码字符串。
本质是字符串拼接的过程。
原理:通过递归AST来生成字符串,最先生成根节点,然后在子节点字符串生成后,将其拼接在根节点的参数中,子节点的自己点拼接在子节点的参数中,这样一层一层地拼接,直到最后拼接成完整的字符串。
不同类型的节点生成字符串的方式不同,有三种类型:
- 元素节点
- 文本节点
- 注释节点
字符串拼接好之后,会将字符串拼在with中返回给调用者。
假设我们有这么一段 template
<template>
<div id="test">
{{val}}
<img src="http://xx.jpg">
</div>
</template>
最终会被转换成这样子的函数字符串
{render: "with(this){return _c('div',{attrs:{"id":"test"}},[[_v(_s(val))]),_v(" "),_m(0)])}"}
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 蒲月阿七 原文链接:https://juejin.im/post/6933967729841930253