开启 deep: true 会导致 watch 在初始化和变更时各进行一次递归操作,引发高成本依赖建立与深层 diff,数据规模稍大即明显卡顿。

开启 deep: true 会让 watch 对整个嵌套结构做两次高成本操作:初始化时递归建立响应式依赖,变更时再递归比对新旧值。这不是“慢一点”,而是数据规模稍大就明显卡顿。
深度监听的两轮递归开销
Vue 的 deep 监听不是“监听一次”,而是一整套链式动作:
- 首次绑定时:遍历目标对象所有层级(包括数组索引、对象属性、嵌套子对象),为每个可访问路径创建依赖追踪点;一个含 500 个节点的树形配置,可能生成 3000+ 个响应式代理和依赖关系
-
每次变更后:不管改的是哪一层,watch 都要从根开始逐层对比(深层 diff),哪怕只改了
user.profile.avatarUrl,也会触发对user→profile→avatarUrl全路径的取值与相等性判断
性能恶化不是线性,而是指数级放大
嵌套越深、分支越多,开销增长越剧烈:
- 2 层嵌套对象(如
{a: {b: 1}}):deep 开销基本可忽略 - 4–5 层 + 数组混合(如表单 schema 或编辑器文档节点):单次变更耗时常达 5–12ms,足以阻塞主线程、造成输入延迟或动画掉帧
- 配合 reactive 包裹的超大对象(如含 200+ 字段的用户设置):首次监听可能卡住渲染,尤其在低端设备上
真正被拖累的不只是 watch 回调
deep 监听会连带加剧整个响应式系统的负担:
- 触发大量 Proxy get 拦截,导致依赖收集膨胀,后续任意相关字段更新都可能引发多余刷新
- 若监听对象本身由
reactive()创建,deep 会重复代理已代理过的子对象,造成代理实例冗余 - 与 computed 或模板中频繁读取同一深层路径叠加时,会放大重复计算和依赖重建
更轻量的替代方案
多数场景下,并不需要监听“任意变化”,只需响应特定路径:
- 用函数式监听精准捕获:
watch(() => user.settings.theme, cb),只追踪这一条路径,耗时通常低于 0.1ms - 把大对象拆成多个细粒度 ref:
userProfile = ref()、userPermissions = ref(),按需监听 - Vue 3.5+ 支持
deep: 3,限制递归深度,平衡覆盖范围与性能 - 静态大数据结构(如菜单配置、原始树)改用
shallowRef+markRaw,彻底跳过响应式追踪











