Vue源码 编译(三)optimize 优化AST树

Vue源码 编译(三)optimize 优化AST树

学习内容和文章内容来自 黄轶老师
黄轶老师的慕课网视频教程地址:《Vue.js2.0 源码揭秘》
黄轶老师拉钩教育教程地址:《Vue.js 3.0 核心源码解析》
这里分析的源码是Runtime + Compiler 的 Vue.js
调试代码在:node_modules\vue\dist\vue.esm.js 里添加
vue版本:Vue.js 2.5.17-beta

你越是认真生活,你的生活就会越美好——弗兰克·劳埃德·莱特
《人生果实》经典语录

点击回到 Vue源码学习完整目录

Vue源码 编译(一)编译入口
我们找到的编译的入口,它主要就是执行了如下几个逻辑:

  • 解析模板字符串生成 AST
const ast = parse(template.trim(), options)
  • 优化语法树
optimize(ast, options)
  • 生成代码
const code = generate(ast, options)

optimize

optimize的目标是通过标记静态根的方式优化重新渲染过程中对静态节点的处理逻辑

optimize的过程就是深度遍历AST树先标记静态节点再标记静态根

单步调试代码

// src/main.js
import Vue from 'vue'

new Vue({
  el: '#app',
  template: `
    <div>
      <ul :class="bindCls" class="list" v-if="isShow">
        <li v-for="(item, index) in data" :key="index" @click="clickItem(index)">
          {{item}}:<{{index}}
        </li>
      </ul>
      <div>
        <p>111</p>
      </div>
      <p>222</p>
    </div>`,
  data() {
    return {
      bindCls: 'a',
      isShow: true,
      data: ['A', 'B', 'C', 'D']
    }
  },
  methods: {
    clickItem(index) {
      console.log(index)
    }
  }
})

在这里插入图片描述

当我们的模板 template 经过 parse 过程后,会输出生成AST 树,那么接下来我们需要对这颗树做优化,optimize 的逻辑是远简单于 parse 的逻辑,所以理解起来会轻松很多。

为什么要有优化过程,因为我们知道 Vue 是数据驱动,是响应式的,但是我们的模板并不是所有数据都是响应式的,也有很多数据是首次渲染后就永远不会变化的,那么这部分数据生成的 DOM 也不会变化,我们可以在 patch的过程跳过对他们的比对

来看一下 optimize 方法的定义,在 src/compiler/optimizer.js 中:

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

function genStaticKeys (keys: string): Function {
  return makeMap(
    'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
    (keys ? ',' + keys : '')
  )
}

在这里插入图片描述

我们在编译阶段可以把一些 AST 节点优化成静态节点,所以整个 optimize 的过程实际上就干 2 件事情,

  • markStatic(root) 标记静态节点 ,
  • markStaticRoots(root, false) 标记静态根。

标记静态节点

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

在这里插入图片描述

AST 元素节点总共有 3 种类型,

  • type 为 1 表示是普通元素
  • type 为 2 表示是表达式
  • type 为 3 表示是纯文本

首先执行 node.static = isStatic(node)

isStatic对一个 AST 元素节点是否是静态的判断

  • 如果是表达式,就是非静态
  • 如果是纯文本,就是静态

对于一个普通元素,如果有pre 属性,那么它使用了 v-pre 指令,是静态

否则要同时满足以下条件:

  • 没有使用 v-ifv-for
  • 没有使用其它指令(不包括 v-once),
  • 非内置组件,是平台保留的标签,
  • 非带有 v-fortemplate 标签的直接子节点,
  • 节点的所有属性的 key 都满足静态 key;

这些都满足则这个 AST 节点是一个静态节点

如果这个节点是一个普通元素,则遍历它的所有 children递归执行 markStatic

因为所有的 elseifelse 节点都不在 children 中, 如果节点的 ifConditions 不为空,则遍历 ifConditions 拿到所有条件中的 block,也就是它们对应的AST 节点,递归执行 markStatic

在这些递归过程中,一旦子节点有不是static 的情况,则它的父节点的 static 均变成 false

标记静态根

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

markStaticRoots 第二个参数是 isInFor,对于已经是 static 的节点或者是 v-once 指令的节点,node.staticInFor = isInFor

接着就是对于 staticRoot 的判断逻辑,从注释中我们可以看到,对于有资格成为 staticRoot 的节点,除了本身是一个静态节点外,必须满足拥有 children,并且 children 不能只是一个文本节点,不然的话把它标记成静态根节点的收益就很小了。

接下来和标记静态节点的逻辑一样,遍历 children 以及 ifConditions,递归执行 markStaticRoots

回归我们之前的例子,经过 optimize 后,AST 树变成了如下:

ast = {
  'type': 1,
  'tag': 'ul',
  'attrsList': [],
  'attrsMap': {
    ':class': 'bindCls',
    'class': 'list',
    'v-if': 'isShow'
  },
  'if': 'isShow',
  'ifConditions': [{
    'exp': 'isShow',
    'block': // ul ast element
  }],
  'parent': undefined,
  'plain': false,
  'staticClass': 'list',
  'classBinding': 'bindCls',
  'static': false,
  'staticRoot': false,
  'children': [{
    'type': 1,
    'tag': 'li',
    'attrsList': [{
      'name': '@click',
      'value': 'clickItem(index)'
    }],
    'attrsMap': {
      '@click': 'clickItem(index)',
      'v-for': '(item,index) in data'
     },
    'parent': // ul ast element
    'plain': false,
    'events': {
      'click': {
        'value': 'clickItem(index)'
      }
    },
    'hasBindings': true,
    'for': 'data',
    'alias': 'item',
    'iterator1': 'index',
    'static': false,
    'staticRoot': false,
    'children': [
      'type': 2,
      'expression': '_s(item)+":"+_s(index)'
      'text': '{{item}}:{{index}}',
      'tokens': [
        {'@binding':'item'},
        ':',
        {'@binding':'index'}
      ],
      'static': false
    ]
  }]
}

我们发现每一个 AST 元素节点都多了 staic属性,并且 type为 1 的普通元素 AST 节点多了 staticRoot 属性。
在这里插入图片描述

总结

那么至此我们分析完了 optimize 的过程,就是深度遍历这个 AST 树,去检测它的每一颗子树是不是静态节点

如果是静态节点则它们生成 DOM 永远不需要改变,这对运行时对模板的更新起到极大的优化作用。

我们通过 optimize 我们把整个 AST 树中的每一个 AST 元素节点标记了 staticstaticRoot,它会影响我们接下来执行代码生成的过程。

Vue源码学习目录

组件化 (一) createComponent
组件化 (二) patch
组件化 (三) 合并配置
组件化 (四) 生命周期
组件化(五) 组件注册

深入响应式原理(一) 响应式对象
深入响应式原理 (二)依赖收集 & 派发更新
深入响应式原理 (三)nextTick & 检测变化的注意事项
深入响应式原理 (四)计算属性 VS 侦听属性
深入响应式原理 (五)深入响应式原理 (五)组件更新
深入响应式原理 (六)Props (v2.6.11)
深入响应式原理 (七)原理图总结

编译(一)编译入口
编译(二)parse 解析模板字符串生成 AST
编译(三)optimize 优化AST树
编译(四)codegen 把优化后的 AST 树转换成可执行的代码

点击回到 Vue源码学习完整目录


谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值