Vue源码阅读(13):优化器

 我的开源库:

优化器的逻辑相比解析器简单了很多,其只做了两件事:标记静态节点和标记静态根节点,所以理解起来会轻松很多。

为什么要做优化操作呢?因为在一个 Vue 应用中,有些页面内容是不随数据的改变而改变的,这些页面内容只需要在初次渲染的时候渲染一次就可以了,所以需要借助优化器打个静态的标记,这样在 patch 的环节,就可以直接跳过静态节点,提高性能。

这一小节,代码解释都在注释中,我写的很详细。

1,src/compiler/optimizer.js ==> optimize

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)
}

第一步标记静态节点,第二步标记静态根节点。

2,src/compiler/optimizer.js ==> markStatic 标记静态节点

// 标记所有的静态节点(从根节点向下)
function markStatic (node: ASTNode) {
  // isStatic 判断某一个节点是不是静态节点
  node.static = isStatic(node)
  if (node.type === 1) {
    // 对子节点进行静态节点标志的处理,因为只有元素节点才有子节点,所以用 if (node.type === 1) 进行判断
    // 不要将自定义组件标记为静态节点,所以在这里,直接 return
    if (
      !isPlatformReservedTag(node.tag) &&
      node.tag !== 'slot' &&
      node.attrsMap['inline-template'] == null
    ) {
      return
    }
    // 对子节点遍历执行 markStatic 函数,
    for (let i = 0, l = node.children.length; i < l; i++) {
      const child = node.children[i]
      markStatic(child)
      if (!child.static) {
        // 如果有一个子节点不是静态节点的话,那么其父节点就肯定不是静态节点
        node.static = false
      }
    }
    // 如果当前的节点绑定了 v-if 的话
    if (node.ifConditions) {
      // 遍历除了它之外的存在于条件链的 AST 节点(可以看到是从下标 1 开始遍历的,而不是 0,下标 0 是 node 本身)
      // 条件链是指这样的代码:
      // <h2 v-if="status == 1">名字是小明</h2>
      // <h3 v-else-if="status == 2">名字2:{{name}}</h3>
      // <h4 v-else>名字是小山</h4>
      // 如果条件链的某个分支节点不是静态节点的话,当前节点就不是静态节点,
      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
        }
      }
    }
  }
}

2-1,借助 isStatic 判断节点是不是静态节点

function markStatic (node: ASTNode) {
  // isStatic 判断某一个节点是不是静态节点
  node.static = isStatic(node)
}

函数开头直接借助 isStatic(node) 判断节点是不是静态节点,isStatic() 函数的源码如下:

// 判断某一个节点是不是静态节点
function isStatic (node: ASTNode): boolean {
  // AST type 解释
  // 1:元素节点
  // 2:含有表达式的文本节点
  // 3:纯文本节点
  if (node.type === 2) { // expression
    // 如果是含有表达式的文本节点的话,肯定不是静态节点
    return false
  }
  if (node.type === 3) { // text
    // 如果是纯文本节点的话,肯定是静态节点
    return true
  }
  // 剩下的就是判断元素节点是不是静态节点
  // 元素节点的判断稍微复杂一些,有很多种情况
  // 一:如果该节点有 v-pre 指令的话,一定是静态节点。
  // 二:如果该节点没有 v-pre 指令的话,则必须满足一系列的条件才能是静态节点。
  //     (1)不能有动态绑定语法(v-,@,:)
  //     (2)元素节点不能有 v-if 和 v-for 或者 v-else。
  //     (3)不能是内建组件(slot、component)
  //     (4)必须是平台上面的标签,例如:web 端的 div、p等等。
  //     (5)元素节点的父级节点不能是带 v-for 的 template,
  //     (6)元素节点上不存在动态节点才会有的属性
  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)
  ))
}

3,src/compiler/optimizer.js ==> 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
    }
    // 递归调用子节点
    // 注意:如果当前节点被认定为静态根节点,就会 return,不会执行到下面的代码,
    //      也就是说,一旦某个节点被认定为静态根节点,将不会再处理他的根节点
    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)
      }
    }
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值