Vue 的响应式系统与虚拟 DOM 构成“感知变化→触发更新→生成新视图→精准落地”闭环:响应式系统通过 getter/setter(Vue 2)或 Proxy(Vue 3)追踪依赖并通知更新;render 函数生成新 VNode 树;diff 算法比对后局部更新真实 DOM。

Vue 的响应式数据和虚拟 DOM 不是各自运行的两套系统,而是一个“感知变化 → 触发更新 → 生成新视图 → 精准落地”的完整闭环。数据变了,界面自动跟着变,靠的就是这个联动机制。
响应式系统负责“发现变化”
Vue 对 data、props、computed 中的值做了封装:Vue 2 用 Object.defineProperty 劫持属性读写,Vue 3 则用 Proxy 代理整个对象。关键在两个阶段:
- 读取时(get):组件首次渲染执行 render 函数,访问到
this.message这类响应式值,就会把当前组件的更新逻辑(effect 或 Watcher)记为它的依赖; - 修改时(set):一旦赋值如
this.message = 'hello',setter 被触发,立刻通知所有依赖它的组件“该重新跑了”。
注意:直接给对象新增属性(obj.newKey = val)或通过索引设置数组项(arr[0] = x)在 Vue 2 中不会被监听,需用 Vue.set 或 this.$set;Vue 3 的 Proxy 天然支持这些操作。
渲染函数生成新的虚拟 DOM 树
被通知更新的组件,不会马上操作真实 DOM,而是等到下一个微任务(nextTick)统一执行自己的 render 函数:
立即学习“前端免费学习笔记(深入)”;
- 这个函数由模板编译而来,比如
<div>{{ message }}</div>编译成返回 VNode 的 JS 函数; - 它再次读取响应式数据,触发 getter,完成新一轮依赖收集(为下次更新做准备);
- 执行结果是一棵全新的虚拟 DOM 树(VNode 树),纯 JS 结构,不涉及真实 DOM。
虚拟 DOM 比对并局部更新真实 DOM
有了新旧两棵 VNode 树,Vue 就启动 diff 算法,逐层对比节点类型、key、props、子节点等:
- 如果只是文本变了,就只替换 innerText;
- 如果 class 切换,就只更新 className 属性;
- 如果某个节点被 v-if 移除,就调用 removeChild 删除对应真实 DOM;
- 完全相同的节点(包括静态内容)会被跳过,不做任何操作。
最终,只有真正有差异的部分才会映射到真实 DOM 上,避免全量重绘,也绕开了手动 DOM 操作的繁琐与风险。
常见失步原因往往卡在这几个环节
视图没更新?通常不是 Vue 失灵,而是联动链条某处断了:
- 数据变更绕过了响应式系统:比如直接修改未声明在 data 中的属性,或用
Object.assign({}, obj)替换整个对象却没用ref/reactive包裹; - 异步时机不对:在 setTimeout 或 Promise.then 里改数据后立即查 DOM,此时 patch 还没发生,得用
nextTick等待; - 依赖未被正确收集:模板里用了
v-if,但条件为 false 时,其中的数据根本没被读取,也就没建立依赖,后续改了自然不触发更新。


















