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函数
可以在 https://astexplorer.net/ 这个网站看 template转ast的结果。如:
Vue静态节点
最终生成的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树上的静态节点和静态根节点。 主要作用是 在patch更新阶段,可以跳过静态节点的更新,优化更新过程。 标记过程:
- 先标记静态节点,将节点的static标记为true
- 再标记静态根节点,将节点的staticRoot标记为true。标记规则:当前节点是静态节点,所有子节点都是静态节点,并且子节点至少有两个以上。
可以在 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方法
- 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