这是我参与更文挑战的第11天,活动详情查看: 更文挑战。
前言
前面我们学习了模板编译中的解析器,这次我们将学习优化器。
优化器
什么是静态子树?
静态子树
指的是那些在 AST
中永远都不会发生变化的节点。例如,一个纯文本节点就是静态子树,而带变量的文本节点就不是静态子树,因为它会随着变量的变化而变化。
好处
标记静态子树有两点好处:
- 每次重新渲染时,不需要为静态子树创建新节点
- 在
虚拟DOM
中打补丁(patching)的过程可以跳过
为什么重新渲染时,不需要为静态子树创建新节点?
前面我们有讲过克隆节点
。在生成VNode的过程中,如果发现一个节点被标记为静态子树,那么除了首次渲染会生成节点之外,在重新渲染时并不会生成新的子节点树,而是克隆已存在的静态子树。
为什么在 patch 阶段可以跳过?
如果两个节点都是静态子树,就不需要进行对比与更新 DOM 的操作,直接跳过。因为静态子树是不可变的,不需要对比就知道它不可能发生变化。此外,直接跳过后续的各种对比可以节省 JavaScript 的运算成本。
内部实现
优化器的内部实现主要分为两个步骤:
- 在AST中找出所有
静态节点
并打上标记 - 在AST中找出所有
静态根节点
并打上标记
类似下面的节点就是静态节点:
<p>我是静态节点<p>
对应到 AST 中,就是节点的static
为 true
,如果是静态根节点,staticRoot
也为true
。
找出所有静态节点并标记
递归 AST,使用 isStatic
函数来判断节点是否是静态节点,然后如果节点的类型等于1
,说明节点是元素节点
,那么循环该节点的子节点,调用 markStatic
函数用同样的处理逻辑来处理子节点:
function markStatic(node){
node.static = isStatic(node);
if (node.type === 1) {
for (let i = 0, l = node.children.length; i<l;i++){
const child = node.children[i];
markStatic(child);
}
}
}
什么样的节点是静态节点?
当模板被解析器解析成AST时,会根据不同元素类型设置不同的type值
type | 说明 | 1 | 元素节点 | 2 | 带变量的动态文本节点 | 3 | 不带变量的纯文本节点 |
---|
显而易见,没有变量的文本节点肯定是静态节点,然后没有使用v-if
、v-else
、v-for
,或没有使用v-bind
也是静态节点。
自定义组件或者内置组件也不会是静态节点。
由于递归是从上向下依次标记的,如果父节点被标记为静态节点之后,子节点却被标记为动态节点,这时就会发生矛盾。因为静态子树中不应该只有它自己是静态节点,静态子树的所有子节点应该都是静态节点。因此,我们需要在子节点被打上标记之后重新校对当前节点的标记是否准确。
function markStatic(node){
node.static = isStatic(node);
if (node.type === 1) {
for (let i = 0, l = node.children.length; i<l;i++){
const child = node.children[i];
markStatic(child);
// 新增
if(!child.static){
node.static = false
}
}
}
}
我们需要判断它是否是静态节点,如果不是,那么它的父节点也不可能是静态节点。
找出所有静态根节点并标记
找出静态根节点的过程与找出静态节点的过程类似,都是使用递归的方式。如果一个节点被判定为静态根节点,那么将不会继续向它的子级继续寻找。
graph
root((root)) --找到--> a((静态))
root --没找到--> b((b))
b --找到--> c((静态))
a --> d((静态))
a --> e((静态))
c --> f((静态))
c --> g((静态))
有一种情况,即便它真的是静态根节点,也不会被标记为静态根节点,因为其优化成本大于收益。这种情况是一个元素节点只有一个文本节点。
例如:
<p>我是静态节点<p>
p元素只有一个文本子节点,此时即便它是静态根节点,也不会被标记。
function markStaticRoots(node){
if (node.type === 1) {
if (node.static && node.children.length && !(node.children.length === 1 && node.children[0].type === 3)){
node.staticRoot = true;
return;
} else {
node.staticRoot = false;
}
if (node.children) {
for (let i = 0, l = node.children.length; i<l;i++){
markStaticRoots(node.children[i])
}
}
}
}
总结
本文,我们学习了优化器的作用和原理,后面我们将学习模板编译的第三部分——代码生成器。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!