在 Vue.js 中,模板 (template) 会被转换成渲染函数 (render function),从而生成虚拟 DOM (Virtual DOM)。这个过程包括以下几个关键步骤:
1. 模板编译 (Template Compilation)
当使用 Vue 的模板语法(<template>
)时,模板会被编译为 JavaScript 渲染函数。这一步的具体过程如下:
-
解析模板 (Parsing)
Vue 的编译器会将 HTML 模板解析为一棵 抽象语法树 (AST) 。这棵树描述了模板的结构及其包含的指令、属性等信息。-
示例模板:
vue 复制代码 <div id="app"> <p>{{ message }}</p> </div>
对应的 AST 树可能类似:
json 复制代码 { "type": "Element", "tag": "div", "attrs": { "id": "app" }, "children": [ { "type": "Element", "tag": "p", "children": [ { "type": "Expression", "content": "message" } ] } ] }
-
-
优化 (Optimization)
- 静态节点会被标记为静态内容。这样在后续的渲染过程中,这些节点就可以跳过更新,提升性能。
- 优化的主要目标是减少运行时的开销。
-
生成代码 (Code Generation)
最后,编译器会将 AST 转换成 JavaScript 渲染函数(render
函数)字符串。
示例:javascript 复制代码 function render() { return _c('div', { attrs: { id: 'app' } }, [ _c('p', [_v(_s(message))]) ]); }
2. 执行渲染函数 (Render Function Execution)
渲染函数在运行时会被执行,生成 虚拟 DOM (VNode)。
_c
是一个内部函数(createElement
),用于创建虚拟节点。_v
表示创建文本节点。_s
用于将数据(如message
)转换为字符串。
生成的虚拟 DOM 树可能如下:
json
复制代码
{
"tag": "div",
"data": { "attrs": { "id": "app" } },
"children": [
{
"tag": "p",
"data": {},
"children": [
{ "text": "Hello, Vue!" }
]
}
]
}
3. 虚拟 DOM 转真实 DOM (VNode to Real DOM)
Vue 使用虚拟 DOM 来描述真实 DOM。当渲染函数返回虚拟 DOM 后,Vue 的虚拟 DOM diff 和 patch 过程会执行以下步骤:
-
初次渲染
虚拟 DOM 树会被转换为真实 DOM,并插入到页面中。 -
数据变化触发更新
- 当响应式数据发生变化时,Vue 会重新执行渲染函数,生成新的虚拟 DOM 树。
- Vue 对新旧虚拟 DOM 树进行 diff 算法 比较,找出变化部分,并高效地更新真实 DOM。
4. 运行时与编译时的区别
- 编译时 (Compiler-included build)
如果使用的是 Vue 的完整版(如通过<script>
引入的版本),模板会在浏览器中被动态编译为渲染函数。 - 运行时 (Runtime-only build)
如果使用的是运行时版本(如通过 Vue CLI 构建的版本),模板需要在构建阶段(比如用 webpack)提前被编译为渲染函数。
总结:Vue 的 template
到 render
的过程是:
-
模板编译为渲染函数。
-
渲染函数执行生成虚拟 DOM。
-
虚拟 DOM 转换为真实 DOM,渲染到页面上。
这种过程结合了编译时优化和运行时的高效更新,从而实现高性能的响应式 UI 渲染。较大概率引出下面的追问:
-
Vue 2 和 Vue 3 模板编译的区别?
- Vue 3 引入 Block Tree 和动态节点标记,减少不必要的 diff。
-
AST 的作用是什么?
- 用来描述模板的结构,为后续优化和代码生成提供基础。
-
静态节点优化的原理是什么?
- Vue 会标记静态节点,在数据更新时跳过它们的重新渲染,提升性能。
-
虚拟 DOM 的作用是什么?
- 提供性能优化的抽象层,通过 diff 算法高效更新真实 DOM。
-
虚拟 DOM diff 的核心思路是什么?
- 逐层对比新旧 VNode,尽量复用 DOM,最小化更新。