vue源码阅读(六)$mount 【compile optimize标记节点】

1
2
3
4
> if (options.optimize !== false) {
> optimize(ast, options)
> }
>

optimize

主要功能就是标记静态节点,为后面 patch 过程中对比新旧 VNode 树形结构做优化。被标记为 static 的节点在后面的 diff 算法中会被直接忽略,不做详细的比较。

optimize的目标:遍历生成的模板AST树并检测纯静态的子树,即永远不需要改变的dom。一旦我们检测到这些子树,我们就可以:

  • 把它们提升成常数,这样我们就不再需要在每次重新渲染时为它们创建新节点;
  • 在修补过程中完全跳过它们

函数如下:

1
2
3
4
5
6
7
8
9
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
// 第一遍:标记所有非静态节点。
markStatic(root)
// 第二遍:标记静态根。
markStaticRoots(root, false)
}

markStatic - 标记所有非静态节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function markStatic (node: ASTNode) {
node.static = isStatic(node)
if (node.type === 1) { // 元素节点
//不要使组件槽内容成为静态的。这避免了:
// 1。无法改变插槽节点的组件
// 2。静态插槽内容无法热重新加载
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}
// 递归遍历所有处理children节点,并判断如果任意子节点不是静态则当前节点也不是静态节点
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
if (!child.static) {
node.static = false
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
markStatic(block)
if (!block.static) {
node.static = false
}
}
}
}
}

isStatic函数 - 初步判断是否是静态节点(元素节点需特殊处理,详见👆)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { // 表达式
return false
}
if (node.type === 3) { // 纯文本
return true
}
// 处理特殊标记
return !!(node.pre || (// v-pre标记的
!node.hasBindings && // 动态绑定
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}

markStaticRoots - 标记静态根

ASTNode 的 type 字段用于标识节点的类型,可查看上一篇的 AST 节点定义:type 为 1 表示元素,type 为 2 表示插值表达式,type 为 3 表示普通文本。可以看到,在标记 ASTElement 时会依次检查所有子元素节点的静态标记,从而得出该元素是否为 static。上面 markStatic 函数使用的是树形数据结构的深度优先遍历算法,使用递归实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor
}
//一个节点如果想要成为静态根,它的子节点不能单纯只是静态文本。否则,把它单独提取出来还不如重渲染时总是更新它性能高
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], isInFor || !!node.for)
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}

总结

optimizer旨在为语法树的节点标上static和staticRoot属性。

遍历第一轮,标记static属性:

  1. 判断node是否为static(有诸多条件)
  2. 标记node的children是否为static,若存在non static子节点,父节点更改为static = false

遍历第二轮,标记staticRoot

  1. 标记static或节点为staticRoot,这个节点type === 1(一般是含有tag属性的节点)
  2. 具有v-once指令的节点同样被标记staticRoot
  3. 为了避免过度优化,只有static text为子节点的节点不被标记为staticRoot
  4. 标记节点children的staticRoot