优化器的作用是在AST中找出静态子树并打上标记 标记静态子树有两点好处:
- 每次重新渲染时,不需要为静态子树创建新节点
如果发现一个节点被标记为静态子树,那么除了首次渲染会生成节点之外,在重新渲染时并不会生成新的子节点树,而是克隆已存在的静态子树
- 在虚拟DOM中打补丁(patching)的过程可以跳过
如果两个节点是静态子树,就不需要对比更新DOM操作,可以节省JS的运算成本。
优化器内部实现步骤:
- 在AST中找到所有静态节点打上标记
- 在AST中找出所有静态根节点打上标记
在AST中静态节点的static属性为true,静态根节点的属性staticRoot为true。
export function optimize (root) {
if (!root) return
// 第一步:标记所有静态节点
markStatic(root)
// 第二步:标记所有静态根节点
markStaticRoots(root)
}
# 找出所有静态节点并标记
function markStatic (node) {
// isStatic函数来判断节点是否是静态节点
node.static = isStatic(node)
// 节点type为1,说明此节点是元素节点,循环该节点的子节点
if (node.type === 1) {
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
// 如果父节点被标记为静态节点之后,子节点却被标记为动态节点,这时就会发生矛盾。因为静态子树中不应该只有它自己是静态节点,静态子树的所有子节点应该都是静态节点。在子节点被打完标记之后,我们需要判断它是否是静态节点,如果不是,那么它的父节点也不可能是静态节点,此时需要将父节点的static属性设置为false。
if (!child.static) {
node.static = false
}
}
}
}
isStatic函数代码如下:
function isStatic (node) {
if (node.type === 2) { // 带变量的动态文本节点
return false
}
if (node.type === 3) { // 不带变量的纯文本节点
return true
}
return !!(node.pre || (
!node.hasBindings && // 没有动态绑定
!node.if && !node.for && // 没有v-if或v-for或v-else
!isBuiltInTag(node.tag) && // 不是内置标签
isPlatformReservedTag(node.tag) && // 不是组件
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
当模板被解析器解析成AST时,会根据不同元素类型设置不同的type值
| type的值 | 说明 |
|---|---|
| 1 | 元素节点 |
| 2 | 带变量的动态文本节点 |
| 3 | 不带变量的纯文本节点 |
# 找出所有静态根节点并标记
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])
}
}
}
}
# 总结
优化器的作用是在AST中找出静态子树并打上标记,这样做有两个好处:
每次重新渲染时,不需要为静态子树创建新节点; 在虚拟DOM中打补丁的过程可以跳过。 优化器的内部实现其实主要分为两个步骤:
(1) 在AST中找出所有静态节点并打上标记;
(2) 在AST中找出所有静态根节点并打上标记。
通过递归的方式从上向下标记静态节点时,如果一个节点被标记为静态节点,但它的子节点却被标记为动态节点,就说明该节点不是静态节点,可以将它改为动态节点。静态节点的特征是它的子节点必须是静态节点。
标记完静态节点之后需要标记静态根节点,其标记方式也是使用递归的方式从上向下寻找,在寻找的过程中遇到的第一个静态节点就为静态根节点,同时不再向下继续查找。
但有两种情况比较特殊:一种是如果一个静态根节点的子节点只有一个文本节点,那么不会将它标记成静态根节点,即便它也属于静态根节点;另一种是如果找到的静态根节点是一个没有子节点的静态节点,那么也不会将它标记为静态根节点。因为这两种情况下,优化成本大于收益