Vue深层嵌套对象的deep侦听器触发时新旧值为何完全一致?
问题核心原因
引用类型的传参本质
- 当对对象配置
deep: true深度侦听时,回调函数拿到的newVal、oldVal并不是两份独立的对象快照,而是直接指向堆内存中同一份对象的引用。深度侦听的触发逻辑本身就是检测这个引用对象内部的属性变化,而非检测引用地址的变化,两个参数从来源上就不是互相独立的副本。
异步执行的时机偏差
- Vue的侦听器回调是异步执行的,会被放入组件的更新队列中,等当前同步执行栈全部跑完才会触发。你点击按钮执行
this.$set(this.myData.time, 'other','testing')的完整流程是:- 同步执行给time对象新增other属性的操作
- 检测到myData的深度变化,把对应的侦听器回调推入异步队列
- 同步代码全部执行完毕,开始执行队列里的侦听器回调
- 等回调真正开始运行时,这块堆内存里的对象数据早就被修改成带新属性的最新状态了,
oldVal作为同内存的引用,读取到的自然是已经更新完的数据,根本没法回溯到修改前的旧状态。 - 对比参考:如果侦听的是字符串、数字这类基本类型值,因为基本类型是值传递,每次修改都会生成独立的新值,回调的
newVal和oldVal完全独立,能正常展示新旧差异;但对象、数组这类引用类型,只要没有替换整个被监听的引用(也就是没有把监听的变量赋值成一个全新的对象/数组),就一定会出现新旧值读取完全一致的情况。
常见解决方式
- 手动维护旧值快照:在执行修改对象的操作之前,先对当前要监听的对象做深拷贝保存为临时变量,等侦听器回调触发时,直接用自己存的这份快照作为旧值使用,完全不依赖回调自带的
oldVal参数。比如修改前先执行const oldSnapshot = JSON.parse(JSON.stringify(this.myData)),再执行$set的修改逻辑。 - 强制触发引用替换:在完成对象内部属性的修改后,将被监听的变量重新赋值为一份全新的对象(比如
this.myData = {...this.myData}),让Vue检测到引用地址的变化,这时候回调传递的oldVal才会真正指向修改前的对象引用。但这种方式会触发额外的组件更新,不适合高频修改的场景。 - 手动做差异对比:如果不需要完整回溯旧值,只需要对比新旧状态的变化,可以在侦听器回调里对
newVal做深拷贝,再结合上一次保存的状态快照做差异计算。
内容的提问来源于stack exchange,提问作者MauirceA
相关产品推荐
相关产品推荐

