深度监听性能开销主要来自初始化依赖收集的递归遍历和数据变更时的深层比对,启用deep: true后会逐层建立依赖并每次更新都递归比对,监听大对象或含非响应式内容时开销剧增,应优先使用精准路径监听、拆分watch或手动diff等轻量替代方案。

深度监听的性能开销主要来自两阶段:初始化依赖收集时的递归遍历,以及数据变更时的深层比对。它不是“慢”,而是做了大量本可避免的工作。
深度监听到底在做什么
启用 deep: true 后,Vue 会:
- 首次监听时,递归访问目标对象每一层属性(包括数组索引、嵌套对象键),为每个响应式字段建立依赖关系;
- 每次数据更新后,再次递归比对新旧值——哪怕只改了一个
user.profile.avatarUrl,也要逐层检查整个user对象; - 若监听对象含数百个嵌套节点或大型数组,单次变更可能触发数千次属性读取和浅比较,CPU 时间明显上升。
哪些情况会让开销急剧放大
以下操作会显著加剧性能压力:
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
- 监听整个
store.state或根级reactive对象,而非具体字段; - 被监听对象中混有非响应式内容(如函数、正则、
Date、Blob),Proxy 仍会尝试遍历,徒增无效开销; - 在高频交互场景(如输入框实时搜索、画布拖拽)中未加节流,导致回调每秒执行数十次;
- 监听一个结构松散、字段极多的表单 schema 或编辑器文档树。
更轻量的替代做法
多数时候,不需要真正“深度”:
立即学习“前端免费学习笔记(深入)”;
- 只关心某个路径?直接用计算属性形式:
watch(() => obj.a.b.c, callback); - 关注数组长度或元素个数变化?监听
arr.length或arr.length即可; - 结构固定且关键字段明确?拆成多个独立 watch,例如分别监听
user.name和user.email; - 需要感知任意子项变更但性能敏感?用
toRaw跳过响应式转换,配合手动 diff(比如比对版本号或时间戳)再触发逻辑。
定位是否真由 deep 引起
别靠猜测,用这几步快速验证:
- 在 watch 回调开头加
console.time('watch'),结尾加console.timeEnd,看耗时是否集中在回调内; - 临时注释掉回调体,观察页面卡顿是否缓解——若依旧卡,说明瓶颈在依赖收集阶段(即 deep 初始化过程);
- 打开 Vue Devtools 的 Performance 面板,筛选 “Watcher” 类型任务,查看其执行时间和调用栈深度;
- 检查被监听对象是否包含大量
markRaw应该排除却没排除的内容。

















