Vue3中修改非响应式属性为何呈现响应式效果?
问题解析:Vue3中修改本地非响应式对象却触发watch捕获修改后值的原因
核心原因
这一现象是引用类型的内存特性与Vue异步更新队列的执行时机共同作用的结果,具体拆解如下:
1. 引用类型的共享内存指向
local_variable是一个普通JS对象(引用类型),当你将它赋值给响应式对象的data属性时:
this.data = local_variable;
此时this.data和local_variable指向同一块内存地址中的对象。后续修改local_variable.name,本质上就是直接修改this.data指向的对象的属性——两者是同一个对象,只是引用名不同。
2. Vue异步更新的执行顺序
Vue的响应式更新(包括watch回调的触发)并非同步执行,而是会被推入微任务队列,等待当前所有同步代码执行完毕后才会执行。在你的第一个示例中:
- 同步代码先完成赋值操作,触发响应式依赖变更,但watch回调不会立即执行;
- 紧接着同步执行
local_variable.name = 'Immediate: updated',直接修改了共享对象的属性; - 当所有同步代码执行完毕后,微任务队列中的watch回调才会触发,此时读取
reactiveObject.data?.name自然是已经修改后的值。
3. 延迟修改的差异
当在赋值与修改之间加入延迟(比如setTimeout)时,修改操作会被推入宏任务队列。而JS的事件循环中,微任务会优先于宏任务执行:
- 同步代码执行赋值后,watch回调进入微任务队列;
- 延迟修改操作进入宏任务队列,需等待微任务全部执行完成后才会触发;
- 因此watch回调执行时,读取的是对象的初始值,后续的修改因为
local_variable不是响应式对象,无法触发新的watch回调。
验证示例
无延迟的代码
<script setup> import { reactive, watch } from 'vue'; class MyClass { data = null; assignAndUpdateImmediate() { const local_variable = { name: 'Immediate: initial' }; this.data = local_variable; local_variable.name = 'Immediate: updated'; } } const reactiveObject = reactive(new MyClass()); watch(() => reactiveObject.data?.name, (newName, oldName) => { console.log(`WATCHER: "${oldName}" -> "${newName}"`); }); reactiveObject.assignAndUpdateImmediate(); </script> <template></template>
输出:
WATCHER: "undefined" -> "Immediate: updated"
带延迟的代码
<script setup> import { reactive, watch } from 'vue'; class MyClass { data = null; assignAndUpdateDelayed() { const local_variable = { name: 'Immediate: initial' }; this.data = local_variable; setTimeout(() => { local_variable.name = 'Immediate: updated'; }, 0); } } const reactiveObject = reactive(new MyClass()); watch(() => reactiveObject.data?.name, (newName, oldName) => { console.log(`WATCHER: "${oldName}" -> "${newName}"`); }); reactiveObject.assignAndUpdateDelayed(); </script> <template></template>
输出:
WATCHER: "undefined" -> "Immediate: initial"
内容的提问来源于stack exchange,提问作者borndeprecated
相关产品推荐
相关产品推荐

