Vue.js 将 VNode 树转为真实 DOM 的过程本质是深度优先遍历(DFS),先创建父节点再递归处理子节点和兄弟节点,确保层级完整性、指令上下文稳定及组件生命周期正确触发。

Vue.js 将 VNode 树转换为真实 DOM 的过程,本质上是通过深度优先遍历(DFS)递归创建和挂载节点,同时兼顾父子关系、指令处理、组件实例化与生命周期钩子的触发。
深度优先遍历的核心逻辑
Vue 2 和 Vue 3 的 patch 过程虽有差异,但都以 DFS 为主线:从根 VNode 开始,先处理当前节点(创建元素或文本节点),再递归处理其 children,最后处理 sibling(兄弟节点)。这种顺序确保父节点总在子节点之前被插入 DOM,符合浏览器渲染树的构建要求。
- 每个 VNode 对应一个真实 DOM 节点(或组件容器),createElm 是核心入口函数(Vue 2)或 patch(Vue 3)
- 遇到
v-if或v-for生成的条件/列表 VNode,只对“有效分支”执行 DFS,跳过 null 或空数组 - 组件 VNode 不直接生成 DOM,而是先 new 组件实例、调用
$mount,再对其子树递归 patch
关键节点的处理顺序(以 Vue 2 为例)
假设一个简单模板: hello,其 VNode 树 DFS 创建顺序如下:
- 创建 元素(真实 DOM 节点),暂不插入文档流
- 遍历 children:第一个子 VNode 是
组件 → 新建组件实例 → 触发beforeCreate→created→ 执行其 render 得到子 VNode 树 → 对该子树再次 DFS- 回到父 div 的第二个子 VNode:
→ 创建 p 元素 → 创建其 text VNode 子节点 "hello" → 插入 p 的 innerHTML
蓝湖代码转vue2.0+element-ui下载将静态 Vue 页面(index.vue)改造为动态页面(Custom.vue);稿源仅限 index.vue、其引用的 CSS 与资源路径,禁止参考仓库内其他文件。Invoke when user wants to transform a static Vue page into a dynamic data-driven page with Element UI components.
- 将已处理完 children 的 div 及其所有后代节点一次性插入父容器(如 body)
为什么必须是深度优先?
DOM 构建依赖层级完整性:
-
appendChild 约束:浏览器 API 要求父元素存在后,才能调用
appendChild添加子元素;DFS 保证父节点先完成创建和初始化 -
指令依赖上下文:如
v-model需要访问父表单元素,v-show依赖父节点样式作用域,DFS 提供稳定的嵌套环境 -
组件作用域链:子组件的
data、computed初始化需在父组件实例就绪后进行,递归调用天然支持该时序
可观察的遍历痕迹
你可以在自定义指令的
bind钩子或组件mounted中打印日志,会发现:- 父组件 mounted 总在子组件 mounted 之后触发(因为子树 patch 完成才轮到父节点插入)
- 同一层级多个组件,按 template 中书写顺序 DFS 进入(即从左到右、从上到下)
- 使用
console.time在createElm开头和结尾打点,能看到明显嵌套耗时结构
这个过程不暴露给用户直接操作,但理解它有助于调试挂载时机、优化递归深度、避免在 created 中访问未挂载的 $refs,以及合理设计异步组件加载策略。
- 遍历 children:第一个子 VNode 是
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










