Vue模板编译原理详解

概要:

Vue有自带编译器的版本和不带编译器的版本,即runtime +complier 和 runtime 版本。编译器的主要作用是将 .vue的模板编译为render函数,因为在开发的时候,写render函数不符合我们的开发习惯,所以我们平常开发用的都是runtime+complier的版本。而项目打包时,就将编译的工作交由webpack来执行打包编译,即打包后的项目已经是编译好的render函数,这样就不需要vue自带的编译器了,即不需要编译部分的代码,可以减少项目体积。

 主要源码

export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  // 把模板 转为 ast抽象语法树,即用树形的方式描述代码结构
  const ast = parse(template.trim(), options)
  // 优化 抽象语法树, 即标记静态节点以及静态根节点,在patch节点时会跳过静态节点的对比与重新渲染
  if (options.optimize !== false) {
    optimize(ast, options)
  }
  // 将抽象语法树 生成 字符串形式的 js代码
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
// 最后将生成的 函数字符串 转为render函数
const render = new Function(code)

可以看到,编译的主要过程为4步:

1、将template字符串 转为ast抽象语法树

2、优化ast抽象语法树

3、将抽象语法树 生成 字符串形式的 js代码

4、最后将生成的 函数字符串 转为render函数

将template字符串 转为ast抽象语法树

可以在 https://astexplorer.net/ 这个网站看 template转ast的结果。如:

最终生成的ast树形对象结构

{
  "type": 1,
  "tag": "template",
  "attrsList": [],
  "attrsMap": {},
  "rawAttrsMap": {},
  "children": [
    {
      "type": 1, // 节点类型 1为标签
      "tag": "div", // 节点标签
      "attrsList": [],
      "attrsMap": {},
      "rawAttrsMap": {},
      "parent": "[Circular ~]",
      "children": [ // 子元素
        {
          "type": 3, // 文本节点
          "text": "Vue", // 文本内容
          "start": 17, // 开始索引
          "end": 20, // 结束索引
          "static": true // 静态节点
        },
        {
          "type": 1,
          "tag": "span",
          "attrsList": [],
          "attrsMap": {},
          "rawAttrsMap": {},
          "parent": "[Circular ~.children.0]",
          "children": [
            {
              "type": 3,
              "text": "静态节点",
              "start": 26,
              "end": 30,
              "static": true
            }
          ],
          "start": 20,
          "end": 37,
          "plain": true,
          "static": true
        }
      ],
      "start": 11,
      "end": 43,
      "plain": true,
      "static": true, // 是否静态节点
      "staticInFor": false,
      "staticRoot": true, // 是否静态根节点
      "staticProcessed": true
    }
  ],
  "start": 0,
  "end": 55,
  "plain": true,
  "static": false,
  "staticRoot": false
}

可以观察到,其实ast语法树就是描述dom树形结构的对象。如描述dom节点的类型,标签,属性,子元素等。

优化ast抽象语法树

主要就是标记ast树上的静态节点和静态根节点。 主要作用是 在patch更新阶段,可以跳过静态节点的更新,优化更新过程。 标记过程:

  • 先标记静态节点,将节点的static标记为true
  • 再标记静态根节点,将节点的staticRoot标记为true。标记规则:当前节点是静态节点,所有子节点都是静态节点,并且子节点至少有两个以上。

生成render方法

可以在 https://template-explorer.vuejs.org/ 查看模板最终生成的render方法。如:

{{ msg }}

编译结果:

code = `with(this) {
    return _c('div', {
      attrs: {
        "id": "app"
      }
    }, [_v(_s(msg))])
}`

然后调用new Function(code),最终生成的render方法:

function render() {
  with(this) {
    return _c('div', {
      attrs: {
        "id": "app"
      }
    }, [_v(_s(msg))])
  }
}

render最终会生成虚拟dom,即vNode,在patch阶段生成真实dom进行挂载。

render函数里的_c、_v、_s是挂载在Vue上的方法,如

Vue._s 其实是toString方法

Vue._v 则是 createTextVNode,用于创建文本元素的vnode节点

总结

编译过程

Vue的模板编译过程其实就是将 模板template内容 转换为 render函数的过程。主要分为3个流程:

  • 将template字符串 转为ast抽象语法树
  • 优化ast抽象语法树,主要是标记一些节点内容不变的节点为静态节点和静态根节点,patch更新对比时会跳过这些节点的比对和重新渲染
  • 将优化好的ast语法树,通过递归的方式,拼接为render方法的字符串,最后执行new Function,转为render方法

runtime + complier版本的渲染流程

  • vue complier将模板字符串转为ast抽象语法树
  • 对ast做静态节点标记,优化ast语法树
  • 通过递归的方式,拼接字符串,将ast转为render方法的字符串,然后用new Function生成最终的render函数
  • render函数将生成对应的虚拟dom,VNode
  • 在组件patch阶段,将生成的VNode生成真实dom元素,添加/更新到dom树上

相关文章

Vue源码解析——Vue初始化及首次渲染过程:https://blog.csdn.net/comedyking/article/details/115551173
Vue源码解析——组件更新过程:https://blog.csdn.net/comedyking/article/details/115670343

Vue-Watcher观察者源码详解:https://blog.csdn.net/comedyking/article/details/117695761

Vue异步更新过程及$nextTick原理详解 :https://blog.csdn.net/comedyking/article/details/117702133

你可能感兴趣的:(Vue源码解析系列)