Vue是如何进行模板编译的?(一)

前言

如果以简单使用者的角度来学习vue,当然了解其相关的概念及使用方式足够,但如果是深度用户的话,或多或少会踩到不少框架及底层设计方式带来的不少的坑。比如,我们理所当然的认为,更新了状态,视图层会自动相应的更新,但现实是假如状态中使用到了数组,则我们经常会遇到更新了数组的值后,视图并没有按预期去自动更新,而需要我们手动去使用this.$set来强制更新视图,这是因为vue设计时使用的监听状态变化方式使用了Object.defineProperty(obj, prop, descriptor),这种方式监听数组会有问题,针对数组状态,vue进行了特别的处理,改写了数组对象的函数对可能改变数组的方法进行监听处理,但当前处理方式会出现监听遗漏的情况如splice等函数;废话不多说,下面一步步来了解vue是如何进行模板编译。

通用的Vue文件格式

<template>
<!-- 模版部分-->
</template>

<script>
// 生命周期函数
</script>

<style lang="scss" scoped rel="stylesheet/scss">
<!-- css样式或者scss/less等样式预处理语言-->
</style>

编译时主要是对template里面的内容进行编译。script可以直接使用,style也是可以直接抽离出来使用或者进行样式预处理。

第一步是将 模板字符串 转换成 element ASTs(parser解析器)

下面是最简单的例子:

<div>
  <p>{{name}}</p>
</div>

上面的模版转化为下面element ASTs

{
  tag: "div"
  type: 1,
  // staticRoot: false,
  // static: false,
  plain: true,
  parent: undefined,
  attrsList: [],
  attrsMap: {},
  children: [
      {
      tag: "p"
      type: 1,
      staticRoot: false,
      // static: false,
      plain: true,
      parent: {tag: "div", ...},
      attrsList: [],
      attrsMap: {},
      children: [{
          type: 2,
          text: "{{name}}",
          // static: false,
          expression: "_s(name)"
      }]
    }
  ]
}

详细的element ASTs (元素抽象语法树)请查看:https://github.com/vuejs/vue/blob/dev/flow/compiler.js

第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(optimizer优化器)

主要是循环把上面的代码的staticRoot及static设为true/false

{
  tag: "div"
  type: 1,
  staticRoot: false,
  static: false,
  plain: true,
  parent: undefined,
  attrsList: [],
  attrsMap: {},
  children: [
      {
      tag: "p"
      type: 1,
      staticRoot: false,
      static: false,
      plain: true,
      parent: {tag: "div", ...},
      attrsList: [],
      attrsMap: {},
      children: [{
          type: 2,
          text: "{{name}}",
          static: false,
          expression: "_s(name)"
      }]
    }
  ]
}

第三步是 使用 element ASTs 生成 render 函数代码字符串(codegen代码生成器)

主要是把上面的element ASTs转成下面的render函数代码串

{
  render: `with(this){return _c('div',[_c('p',[_v(_s(name))])])}`
}

格式化后如下(_c其实已经跟大名顶顶的各通用virtual dom中创建vnode节点h函数结构非常像了,在这里,_c是createElement,执行后也是创建vnode节点):

with(this){
  return _c(
    'div',
    [
      _c(
        'p',
        [
          _v(_s(name))
        ]
      )
    ]
  )
}

这些缩写函数定义如下:

/*处理v-once的渲染函数*/
  Vue.prototype._o = markOnce
  /*将字符串转化为数字,如果转换失败会返回原字符串*/
  Vue.prototype._n = toNumber
  /*将val转化成字符串*/
  Vue.prototype._s = toString
  /*处理v-for列表渲染*/
  Vue.prototype._l = renderList
  /*处理slot的渲染*/
  Vue.prototype._t = renderSlot
  /*检测两个变量是否相等*/
  Vue.prototype._q = looseEqual
  /*检测arr数组中是否包含与val变量相等的项*/
  Vue.prototype._i = looseIndexOf
  /*处理static树的渲染*/
  Vue.prototype._m = renderStatic
  /*处理filters*/
  Vue.prototype._f = resolveFilter
  /*从config配置中检查eventKeyCode是否存在*/
  Vue.prototype._k = checkKeyCodes
  /*合并v-bind指令到VNode中*/
  Vue.prototype._b = bindObjectProps
  /*创建一个文本节点*/
  Vue.prototype._v = createTextVNode
  /*创建一个空VNode节点*/
  Vue.prototype._e = createEmptyVNode
  /*处理ScopedSlots*/
  Vue.prototype._u = resolveScopedSlots

  /*创建VNode节点*/
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

最后

本文主要是通过一些简单的模板编译例子来介绍模板编译的过程及步骤,其实并没有涉及代码实现部分。但通过本文我们可以了解到vue进行模板编译到底在做什么。结合前面的一些virtual dom及mvvm文章,如果串联起来我们可以对mvvm框架有一定的想象空间了。但实现起来复杂度远远没有这么简单,下一篇,我们将对vue模板编译的原理进行详细探索哈。

 

参考引用:
https://segmentfault.com/a/1190000013763590
https://www.cnblogs.com/answershuto/p/7638755.html

作者: 博主

Talk is cheap, show me the code!

发表评论

电子邮件地址不会被公开。

Captcha Code