watch 默认监听不到复杂对象的增删改,因Vue 2 依赖 Object.defineProperty 仅代理初始属性,Vue 3 虽用 Proxy 但 watch 默认浅监听且对非响应式操作不敏感;深克隆+对比可绕过响应式限制实现精准变更感知。

直接用 watch 监听复杂对象(如嵌套的数组、对象)时,Vue 默认的浅监听无法捕获内部属性的增删改——比如往对象里加个新字段、删除某个 key、或修改深层嵌套值,handler 都不会触发。想真正响应这些变化,核心思路不是“监听得更深”,而是“让变化可被检测”:用深克隆做快照对比,手动识别差异。
为什么 watch 默认监听不到增删改?
Vue 2 的响应式依赖于 Object.defineProperty,只对初始化时已存在的属性建立 getter/setter;Vue 3 虽用 Proxy 支持新增/删除,但 watch 默认仍为浅监听(deep: false),且对 delete 或 Object.assign 等非响应式操作不敏感。更关键的是:即使开启 deep: true,它也只能追踪已有响应式路径的变化,对运行时动态添加的属性或直接替换整个子对象,依然可能漏掉。
用深克隆 + 对比实现精准变更感知
不依赖 Vue 的响应式机制去“捕捉”变化,而是每次变化后主动抓取当前快照,与上一次深克隆副本比对,差分出哪些键被新增、删除或修改。适合配置项管理、表单脏检查、本地缓存同步等场景。
- 在 data 或 setup 中维护一个深克隆的“上一状态”:
const prevData = _.cloneDeep(currentObj)(推荐用 Lodash 的cloneDeep,或手写简易版) - 用
watch监听目标对象,开启immediate: true和deep: true,确保首次和后续变化都被捕获 - 在 handler 中执行深克隆当前值,并调用对比函数(如
_.isEqual(prevData, currentData)判断是否真有变;若不同,再用_.differenceWith或递归遍历找出具体差异) - 更新
prevData为当前克隆值,为下次对比做准备
注意深克隆的边界与性能
深克隆不是银弹。遇到循环引用会报错,遇到 Date、RegExp、Map、Set、函数等原生类型可能丢失行为,大数据量时有性能开销。
- 避免对整个大型列表无差别克隆——只克隆你关心的字段,或用
JSON.parse(JSON.stringify())快速处理纯 JSON 数据(但会丢函数、undefined、Symbol) - 若仅需判断“是否改动过”,用
JSON.stringify对比字符串(前提是结构稳定、顺序一致、不含不可序列化内容) - 对高频变动的对象(如实时编辑器内容),考虑节流 + 差分算法(如 jsondiffpatch),而非每次全量克隆
替代方案:用 reactive + customRef 或 proxy 手动控制
如果需要更细粒度的拦截(比如只关心某几个字段的增删),可以不用 watch,改用 Proxy 包裹原始对象,在 set/deleteProperty 拦截器中触发自定义逻辑。Vue 3 的 customRef 也支持返回带 track/trigger 控制的 ref,适合封装高定制监听器。
- 适用于需要精确控制响应时机、或集成第三方状态库的场景
- 相比深克隆,内存占用低、无序列化限制,但开发成本略高
- 注意 Proxy 不兼容 IE,且对数组索引赋值(
arr[0] = x)和 length 修改仍需额外处理

















