vue2从template到render:optimize

175 阅读2分钟

获取到 ast 以后,还需要对当前ast进行优化。

入口为optimize(ast, options):

import { makeMap, isBuiltInTag, cached, no } from 'shared/util'

let isStaticKey
let isPlatformReservedTag

const genStaticKeysCached = cached(genStaticKeys)

/**
 * Goal of the optimizer: walk the generated template AST tree
 * and detect sub-trees that are purely static, i.e. parts of
 * the DOM that never needs to change.
 *
 * Once we detect these sub-trees, we can:
 *
 * 1. Hoist them into constants, so that we no longer need to
 *    create fresh nodes for them on each re-render;
 * 2. Completely skip them in the patching process.
 */
export function optimize (root: ?ASTElement, options: CompilerOptions) {
  if (!root) return
  isStaticKey = genStaticKeysCached(options.staticKeys || '')
  isPlatformReservedTag = options.isReservedTag || no
  // first pass: mark all non-static nodes.
  markStatic(root)
  // second pass: mark static roots.
  markStaticRoots(root, false)
}

从文中注释可以解读优化的目的是:遍历通过template生成的ast树,并且检测到树中的静态节点,比如:那些不需要改变的DOM部分。一旦我们检测到检测到子树:

  • 1、将它们提升为常量,这样我们就不再需要在每次重新渲染时为它们创建新的节点;
  • 2、在patch的过程中完全跳过它们。
    optimize阶段,主要有两个过程markStatic(root)markStaticRoots(root, false):

一、markStatic

function markStatic (node: ASTNode) {
  node.static = isStatic(node)
  if (node.type === 1) {
    // do not make component slot content static. this avoids
    // 1. components not able to mutate slot nodes
    // 2. static slot content fails for hot-reloading
    if (
      !isPlatformReservedTag(node.tag) &&
      node.tag !== 'slot' &&
      node.attrsMap['inline-template'] == null
    ) {
      return
    }
    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
        }
      }
    }
  }
}
function isStatic (node: ASTNode): boolean {
  if (node.type === 2) { // expression
    return false
  }
  if (node.type === 3) { // text
    return true
  }
  return !!(node.pre || (
    !node.hasBindings && // no dynamic bindings
    !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)
  ))
}

(1)首先,通过node.static = isStatic(node)的方式判断是否是静态节点,静态节点的判断分支有:

  • 表达式时为非静态节点
  • 文本为静态节点
  • v-pre标签为静态标签
  • 不是动态绑定,没有v-ifv-for,不是内置标签(slot/component),是html类或者SVG标签、在父级向上未找到for循环的template,并且每一个node属性都是isStaticKey标签。

(2)然后,如果不是平台保留标签,也不是slot或者inline-template,那么直接返回。否则,获取子元素node.children进行递归处理,有一个子元素block.staticfalse,当前节点的static也为false

(3)如果当前node中有ifConditions,则对其中的block部分依然执行递归操作,根据子节点的child去修改当前nodestatic

二、markStaticRoots

function markStaticRoots (node: ASTNode, isInFor: boolean) {
  if (node.type === 1) {
    if (node.static || node.once) {
      node.staticInFor = isInFor
    }
    // For a node to qualify as a static root, it should have children that
    // are not just static text. Otherwise the cost of hoisting out will
    // outweigh the benefits and it's better off to just always render it fresh.
    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)
      }
    }
  }
}

如果node是静态节点或者是v-once,那么将其node.staticInFor设置为false。 再从注释可以看出,要想让其成为静态节点,那么该节点应该有不仅仅是静态的文本节点。否则,将其设置为静态节点的消耗将大于收益,不如将其实时更新。如果存在node.children或者node.ifConditions则进行递归处理。

小结:

静态节点和静态根的静态属性的添加都是一个递归的过程,并且静态节点的属性依赖于其子节点是否全部是静态节点。