Vue通过Proxy拦截读写操作实现响应式:get时收集依赖,set时触发更新,精准通知订阅者,克服Object.defineProperty无法监听新增属性等缺陷。

状态变更触发视图刷新,不是“改了就立刻重绘”,而是经过响应式追踪、虚拟节点比对、差异应用三步完成。核心在于:变更必须被 Vue 的响应式系统捕获,且 patch 只作用于真正变化的 DOM 节点,不重绘整块区域。
状态变更如何被识别
Vue 通过 getter/setter 或 Proxy 拦截对响应式数据的访问与赋值。当组件中 data、computed 或 ref 中的值被修改时,会通知依赖它的 Watcher;该 Watcher 对应组件的 render 函数,从而触发重新求值,生成新的 VNode。
- 非响应式字段(如直接给对象新增属性)不会触发更新,需用
Vue.set(Vue 2)或reactive/ref显式声明 - 异步赋值(如 Promise.then 中修改 data)仍能触发,因为响应式拦截发生在赋值瞬间,与调用栈无关
- 深层对象属性修改也会被侦测,前提是整个对象已被纳入响应式系统(如用
reactive({ a: { b: 1 } }))
新旧 VNode 如何比对(diff)
patch 不是逐像素对比,而是基于 JavaScript 对象结构的逻辑比对。Vue 先判断是否为同一节点(sameVnode),依据是 tag、key、isComment、data.key 等关键标识。只有判定为“同一个节点”,才会进入深度 diff;否则直接销毁旧节点、挂载新节点。
- key 是列表渲染的生命线:缺失或重复 key 会导致节点复用错乱,比如输入框失焦、动画中断、状态错位
- 静态节点(
static标记)跳过比对,直接复用真实 DOM,提升性能 - 文本节点只比对
vnode.text,标签节点则逐项比对 props、class、style、事件监听器等
差异如何转化为真实 DOM 更新
diff 结果是一组最小化操作指令,例如 “插入第3个子节点”“更新第2个节点的 class”“移除第1个子节点”。这些指令由平台适配层(如 nodeOps)执行,屏蔽浏览器/Weex/小程序等底层差异。
- 更新过程不阻塞主线程,所有 DOM 操作被批量提交,在下一个 microtask(
Promise.then)或 macrotask(setTimeout)中统一执行 - 组件级 patch 会递归向下:父组件 patch → 触发子组件
update→ 子组件生成新 VNode → 再次 patch - 若子组件使用
v-once或shouldComponentUpdate返回 false(在 Composition API 中对应shouldUpdate),则跳过其内部 diff
常见误区与注意事项
状态变了但视图没刷新,往往不是 patch 失效,而是变更未进入响应式管道;而视图反复刷新,则多因副作用闭环导致 watcher 循环触发。
- 避免在
computedgetter 或watchhandler 中直接修改被依赖的数据 - 不要在模板中调用方法返回动态值(如
{{ getDate() }}),它每次 render 都执行,且不被缓存 - 大量数据更新时,可借助
Object.freeze冻结静态字段,减少 getter 拦截开销 - 调试可用
vm.$nextTick(() => { /* DOM 已更新 */ })确保获取最新渲染结果


















