转载

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的结果。如:
<template>
 <div>Vue<span>静态节点</span></div>
</template>
最终生成的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方法。如:
<div id="app">{{ msg }}</div>
编译结果:
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个流程:
 1 将template字符串 转为ast抽象语法树
 2 优化ast抽象语法树,主要是标记一些节点内容不变的节点为静态节点和静态根节点,patch更新对比时会跳过这些节点的比对和重新渲染
 3 将优化好的ast语法树,通过递归的方式,拼接为render方法的字符串,最后执行new Function,转为render方法

runtime + complier版本的渲染流程

1 vue complier将模板字符串转为ast抽象语法树
2 对ast做静态节点标记,优化ast语法树
3 通过递归的方式,拼接字符串,将ast转为render方法的字符串,然后用new Function生成最终的render函数
4 render函数将生成对应的虚拟dom,VNode
5 在组件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
正文到此结束
Loading...