Vue模板不是直接操作DOM,而是编译成渲染函数。本文拆解模板编译三阶段:解析生成AST、优化静态节点、生成代码,并说明with语句与作用域提升如何影响运行时性能。
Vue,模板编译,AST,渲染函数,源码
模板不是魔法,是编译
Vue 的模板语法写起来像 HTML,但浏览器不认识 v-if 和 {{ }}。真正发生在背后的事是:模板被编译成 渲染函数,执行后返回虚拟 DOM。
理解这一步,能解释很多“为什么不能写复杂表达式”“为什么 v-if 和 v-for 不推荐同级”的问题。
编译入口:$mount 做了什么
以完整版 Vue 为例,$mount 里会判断是否存在 render 函数。没有就走进 compileToFunctions。核心调用链是 baseCompile(template, options),它返回 { ast, render, staticRenderFns }。
三阶段拆解
解析:HTML 变成 AST
解析器用正则和栈结构逐字符扫描模板。遇到开始标签就 elementStack.push,遇到结束标签就配对弹出。属性解析会区分普通属性、指令(v- 前缀)和修饰符。
最终得到一棵 AST 节点树,每个节点携带 tag、attrsList、children、parent 等字段。注意:AST 是编译中间产物,不在运行时存在。
优化:标记静态节点
optimize(ast, options) 遍历 AST,给永远不变的节点打上 static: true。判断标准很严格:节点本身无动态绑定,且所有子节点都是静态的。
被标记的静态节点在后续 patch 中会被 跳过 diff,这是 Vue 性能优于“全量对比”的关键之一。静态根节点还会被提取到 staticRenderFns 数组,避免每次渲染重新创建。
生成:AST 转代码字符串
generate(ast, options) 递归遍历,调用 genElement 拼出类似这样的字符串:
with(this){return c(‘div’,{attrs:{“id”:”app”}},[v(“hello”)])}
c 是 createElement,v 是 createTextVNode。生成的是字符串,再通过 new Function(code) 变成可执行函数。
with 的争议与替代
with(this) 让模板里的变量直接映射到组件实例,写起来简洁。但它使作用域链变长,且严格模式下不可用。
Vue 3 移除了 with,改用 代理对象 + 手动作用域提升。通过 _ctx 显式访问,并把静态提升的节点缓存在渲染函数外部。这是编译策略的一次重要演进。
编译时与运行时的边界
明白这条边界,就知道:
- 模板里的表达式无法访问
window,因为with只绑定组件实例; v-if优先级低于v-for在 Vue 2 中成立,Vue 3 调整了顺序;- 大量静态内容不会拖慢更新,因为它们在编译时就被标记并跳过。
读模板源码的收益,不是背下函数名,而是建立“模板→AST→代码→渲染函数”的完整心智模型。下次遇到渲染性能问题,你会知道该往编译产物里看一眼。

