Vue虚拟DOM是响应式数据变化触发、经依赖收集与派发更新、VNode比对(diff)及patch操作、最终由nextTick调度真实DOM变更的协同机制。

Vue 的虚拟 DOM 不是独立存在的黑盒,而是从响应式数据变化开始,经由依赖收集、派发更新、补丁比对到真实 DOM 操作的一整套协同机制。理解它,关键在于看清“谁触发更新”“谁决定重渲染”“谁执行 DOM 变更”这三步如何咬合。
响应式系统:更新的起点
Vue 2 使用 Object.defineProperty,Vue 3 改用 Proxy,目标一致:拦截对响应式数据(data、ref、reactive)的读写。读取时进行依赖收集(将当前正在执行的渲染函数记为该数据的订阅者),赋值时触发通知(notify),唤醒所有关联的 watcher 或 effect。
- 不是所有对象都响应式——只有通过
reactive()或ref()创建的数据才进入追踪系统 - 深层属性默认可响应,但直接替换整个对象(如
obj = {...})会丢失响应性,需用Vue.set(Vue 2)或proxy能力保障(Vue 3) - 计算属性(computed)本质是带缓存的响应式 effect,只在依赖变更时重新求值
渲染函数与 VNode:抽象层的桥梁
模板(template)最终被编译为 render 函数,每次执行返回一个描述 UI 结构的 JavaScript 对象——即 VNode(虚拟节点)。它包含标签名、子节点、props、key、指令等信息,但不操作真实 DOM。
-
h()是创建 VNode 的底层 API(如h('div', { id: 'app' }, 'Hello')),JSX 和模板最终都转为此形式 - key 不仅用于列表复用,更是 diff 算法的锚点;无 key 时 Vue 采用就地更新策略,可能导致状态错位
- VNode 是轻量对象,可被序列化、调试、甚至跨平台复用(如服务端渲染、小程序适配)
Diff 算法与 patch:最小化真实 DOM 操作
新旧 VNode 树对比(diff)不是全量递归,而是基于两个假设优化:1)同一层级节点类型突变概率低;2)列表中元素 identity 由 key 唯一标识。因此 Vue 采用双端对比 + key 映射表策略,时间复杂度接近 O(n)。
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
立即学习“前端免费学习笔记(深入)”;
- 组件更新时,先比对 vnode 类型:相同则执行
patch(递归比子节点),不同则直接卸载旧组件、挂载新组件 - 文本节点变更直接更新
textContent;元素节点变更则按需设置/移除属性、class、style、事件监听器 - 真实 DOM 操作被批量聚合在 nextTick 微任务中执行,避免频繁重排重绘
nextTick:异步更新队列的调度中枢
数据变更不会立刻触发 render,而是被推入一个异步队列,在当前 JS 执行栈清空后、下一个事件循环前统一处理。这个机制由 nextTick 封装,底层优先使用 Promise.then / MutationObserver,降级为 setTimeout。
- 在
watch或生命周期钩子中读取 DOM 尺寸/位置,必须用nextTick(() => {...})确保 DOM 已更新 - 多次同步赋值(如
a = 1; b = 2;)只会触发一次更新,因 watcher 被缓存去重 - 手动调用
vm.$forceUpdate()(Vue 2)或triggerRef()(Vue 3)可绕过响应式系统强制刷新,但应避免滥用
这套机制环环相扣:响应式触发更新信号 → 渲染函数产出新 VNode → Diff 找出差异 → Patch 应用到真实 DOM → nextTick 保证时机合理。它不是魔法,而是一组约束清晰、职责分明的设计选择。

















