Vue响应式系统与虚拟DOM渲染通过依赖收集→派发更新→重新渲染→patch链条联动:数据劫持感知变化,render生成VNode,diff对比后patch更新真实DOM。

Vue 的响应式系统和虚拟 DOM 渲染流程是紧密耦合的:数据劫持负责“感知变化”,虚拟 DOM 负责“计算差异”,而 Patch(打补丁)是把差异真正应用到真实 DOM 的关键环节。它们不是独立运行,而是通过依赖收集 → 派发更新 → 重新渲染 → 对比 patch 这一链条联动起来。
数据劫持:让属性访问可追踪
Vue 2 使用 Object.defineProperty,Vue 3 使用 Proxy,目标一致:在读取(getter)时收集当前正在执行的渲染函数作为依赖,在设置(setter)时通知这些依赖更新。
例如:data() { return { count: 0 } } 中的 count 被劫持后,模板中 {{ count }} 渲染时会触发 getter,此时 Vue 记下“这个渲染函数依赖 count”。
- 每个响应式属性对应一个
Dep实例,管理一组Watcher(Vue 2)或ReactiveEffect(Vue 3) - 组件挂载时,会创建一个渲染 watcher/effect,它包裹了
render()函数,并在首次执行时触发所有模板中用到的数据读取,完成依赖收集
响应式触发更新:setter 唤醒依赖
当 this.count = 1 被赋值,setter 触发,对应 Dep 通知所有收集到的 watcher/effect 重新执行。
立即学习“前端免费学习笔记(深入)”;
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
注意:这里不会立即操作 DOM,只是标记“需要重新渲染”。Vue 会将更新任务推入异步队列(nextTick 微任务),批量处理,避免重复渲染。
- watcher/effect 重新运行时,会再次调用
render(),生成一个新的虚拟 DOM 树(VNode) - 旧的 VNode 来自上一次渲染,保存在组件实例的
_vnode(Vue 2)或subTree(Vue 3)上
虚拟 DOM 对比与 Patch:从新旧 VNode 到真实 DOM
新的 VNode 生成后,Vue 调用 patch(oldVNode, newVNode),进入 diff 算法流程:
- 先比对节点类型:标签名、key、是否为组件等;类型不同则直接卸载旧节点、挂载新节点
- 类型相同时,递归比对子节点:使用双端对比(Vue 2)或更精细的索引映射(Vue 3 的 block tree + patchFlags)提升效率
- 对需要更新的属性(class、style、事件、props)、文本内容、子节点顺序等,生成最小粒度的操作指令(如
setAttr、setText、insert、remove)
最终这些指令作用于真实 DOM,完成视图更新 —— 这就是 Patch 的实质。
联动的关键桥梁:render 函数与 effect/watcher
整个链路能跑通,核心在于 render() 不仅返回 VNode,还“主动读取”响应式数据,从而被依赖收集机制捕获。没有这个读取行为,setter 就找不到要通知谁。
换句话说:响应式是驱动源,render 是响应载体,VNode 是中间表示,Patch 是落地动作。
- computed、watch、生命周期钩子等也走同一套响应式触发逻辑,但它们不产生 VNode,因此不触发 patch
- 只有渲染 effect(即组件的 render 函数包裹的 effect)才会在更新后触发 patch 流程
- 如果模板里没用到某个响应式字段(比如只在 method 里用了),它的变化就不会触发当前组件重新渲染

















