【Vue2源码】模板编译compile原理(1)【template str->ast->优化ast->render->虚拟DOM更新】

572 阅读4分钟

Vue 源码目录

  1. 响应式原理
  2. 模板编译原理
  3. 异步更新原理
  4. diff算法原理
  5. Mixin混入原理
  6. Vue-router原理
  7. 生命周期的原理
  8. ...

本文目录

  1. 模板编译原理

  2. Vue2 的版本

  3. 参考 3.1 有不错的视频 Vue官方教程笔记- 尤雨溪手写mini-vue

  4. 相关图解

  5. 总结

一、模板编译原理

template模板字符串 -> ast树-> 优化语法树(标记是否是静态节点)-> 转换得到新的ast-> render函数

AST(抽象语法树) 在开发过程中扮演一个非常重要的角色,但是我们却很少去直接接触它。
无论是代码编译(babel),打包(webpack),代码压缩,css预处理,代码校验(eslint),代码美化(pretiier),Vue中对template的编译,这些的实现都离不开AST。

vue模板编译过程

Vue 提供了 2 个版本,一个是 Runtime + Compiler ,另一个是 Runtime only 的,前者是包含编译代码的,会把编译的过程放在运行时做,后者是不包含编译代码的,需要借助 webpack 的vue-loader把模板编译render函数。不管使用哪个版本,都有一个环节,就是将模板编译成render函数

我们常用的是运行时版本(不包含编译代码)

Vue 单文件SFC -> webpack (vue-loader) -> 将模板编译成render函数

1) vue模板的编译过程分为3个阶段:

image.png

1.1) 解析(Parse)
const ast = parse(template.trim(), options)

将模板字符串解析生成 AST,这里的解析器是vue自己实现的,解析过程中会使用正则表达式对模板顺序解析,当解析到开始标签、闭合标签、文本的时候都会有相对应的回调函数执行,来达到构造 AST 树的目的。 生成的AST 元素节点总共有 3 种类型,1 为普通元素, 2 为表达式,3为纯文本

例如

<ul :class="bindCls" class="list" v-if="isShow">
    <li v-for="(item,index) in data" 
        @click="clickItem(index)">{{item}}:{{index}}
    </li>
</ul>

上面模板解析生成的AST树如下:

ast = {  
      'type': 1,
      'tag': 'ul',
      'attrsList': [],
      'attrsMap': {    
          ':class': 'bindCls',
          'class': 'list',
          'v-if': 'isShow'
       },
       'if': 'isShow'
       ......
   }
1.2) 优化语法树(Optimize)【是否是静态节点做标记】

vue模板中并不是所有数据都是响应式的,有很多数据是首次渲染后就永远不会变化的,那么这部分数据生成的 DOM 也不会变化,我们可以在patch的过程跳过对他们的比对.

此阶段会深度遍历生成的 AST树,检测它的每一颗子树是不是静态节点,如果是静态节点则它们生成 DOM 永远不需要改变,这对运行时对模板的更新起到极大的优化作用.确保静态的数据不会进入虚拟 DOM 的更新阶段,以此来优化性能

遍历过程中,会对整个 AST 树中的每一个 AST 元素节点标记static和staticRoot(递归该节点的所有children,一旦子节点有不是static的情况,则为false,否则为true).

经过该阶段,上面例子中的ast会变成:


ast = {  
      'type': 1,
      'static': false,// 每个节点都会新增此属性
      'tag': 'ul',
      'attrsList': [],
      'attrsMap': {    
          ':class': 'bindCls',
          'class': 'list',
          'v-if': 'isShow'
       },
       'if': 'isShow'
       ......
   }
1.3) 生成代码,将ast生成render函数
const code = generate(ast, options)

通过generate方法,将ast生成render函数:

with(this){
  return (isShow) ?
    _c('ul', {
        staticClass: "list",
        class: bindCls
      },
      _l((data), function(item, index) {
        return _c('li', {
          on: {
            "click": function($event) {
              clickItem(index)
            }
          }
        },
        [_v(_s(item) + ":" + _s(index))])
      })
    ) : _e()
}

看到这里一堆的下划线肯定很懵逼,这里的 _c 对应的是虚拟 DOM 中的 createElement 方法。其他的下划线方法在 core/instance/render-helpers 中都有定义,每个方法具体做了什么不做展开。

image.png

二、Vue2 的版本

很多人使用 Vue 的时候,都是直接通过 vue-cli 生成的模板代码,并不知道 Vue 其实提供了两个构建版本。

  • vue.js: 完整版本,包含了模板编译的能力;
  • vue.runtime.js: 运行时版本,不提供模板编译能力,需要通过 vue-loader 进行提前编译。

image.png

image.png

简单来说,就是如果你用了 vue-loader ,就可以使用 vue.runtime.min.js,将模板编译的过程交过 vue-loader,如果你是在浏览器中直接通过 script 标签引入 Vue,需要使用 vue.min.js,运行的时候编译模板。

三、参考

四、相关图解

image.png

image.png

五、总结

  1. 我们常用的是Vue的运行时版本 Vue 单文件SFC -> webpack (vue-loader) -> 将模板编译成render函数

  2. template模板字符串 -> ast树-> 优化语法树(标记是否是静态节点)-> 转换得到新的ast-> render函数

  3. 【template str -> ast -> 优化ast -> render -> VNode -> patch与之前的VNode -> 得出差异后将这些差异渲染到真实的DOM】