Proxy 默认只代理目标对象本身,不自动递归代理嵌套对象,因此深层属性修改无拦截、无依赖收集、UI不更新。

Proxy 本身不自动深度代理嵌套对象,必须手动递归处理;否则只监听第一层属性,深层修改完全静默。
为什么 new Proxy(obj, handler) 默认不响应深层属性变化
Proxy 只代理目标对象本身,不自动代理其内部的子对象或数组。当你访问 state.user.profile.name 时,profile 和 name 都是原生对象/原始值,没被任何 Proxy 包裹,get 拦截器根本不会触发。
常见错误现象:
- 修改
state.user.profile.name = 'Alice',控制台无更新属性: name = Alice输出 - UI 不刷新,因为
track没在profile.name上建立依赖
解决思路:在 get 拦截中,对返回值是对象(且非 null、非 Date/RegExp 等原生类型)的,主动用 reactive 再包一层。
如何在 get 中安全递归代理子对象
关键不是“一上来就全递归”,而是“按需代理”——只在属性被读取且值为普通对象时,才创建子 Proxy。这样避免初始化开销,也防止循环引用爆栈。
实操建议:
- 用
WeakMap缓存已代理过的对象,避免重复代理同一对象 - 判断是否需要代理:用
typeof value === 'object' && value !== null && !value.__v_isReactive(加个标记防死循环) - 对
Array、Map、Set等内置类型,Vue 3 是单独处理的;简易版可先只处理 plain object
示例片段:
const reactiveMap = new WeakMap()
function reactive(obj) {
if (obj && obj.__v_isReactive) return obj
if (!obj || typeof obj !== 'object') return obj
if (reactiveMap.has(obj)) return reactiveMap.get(obj)
const handler = {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// 仅对普通对象做递归代理
if (typeof res === 'object' && res !== null && !res.__v_isReactive) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, reactive(value), receiver)
console.log(`更新属性: ${key} =`, value)
return result
}
}
const proxy = new Proxy(obj, handler)
obj.__v_isReactive = true
reactiveMap.set(obj, proxy)
return proxy
}
set 拦截里为什么要对新值调用 reactive(value)
否则赋值一个新对象(如 state.user = { profile: { name: 'Bob' } })后,这个新对象仍是普通对象,后续改 state.user.profile.name 就又失效了。
性能与兼容性影响:
- 每次
set都触发一次reactive调用,但因有WeakMap缓存,重复对象不会重复代理 - 原始值(string/number/boolean)直接透传,不额外包装
- 若值是
null或undefined,跳过代理,避免报错
注意:这种写法不处理数组索引赋值(如 arr[0] = {...}),也不拦截 push、pop 等方法——这些需单独代理 Array 原型方法,属于进阶需求。
驱动 UI 更新时最容易漏掉的环节:依赖收集与触发时机
上面的 reactive 只做了拦截,还没连接“数据变 → 视图重绘”。真正驱动 UI,必须配合一个副作用注册机制(类似 Vue 的 effect)。
容易踩的坑:
- 在
get里只打印日志,没把当前正在执行的函数(比如渲染函数)存到依赖列表,set时就找不到谁该更新 - 多个
get嵌套调用时,依赖收集错层(比如render()里读a.b.c,结果只记了a的依赖) - 没有用
Promise.resolve().then()或queueMicrotask批量异步更新,导致多次修改触发多次 DOM 操作
简言之:Proxy 是“监听器”,但“谁监听了谁”和“监听到之后干啥”,得靠外部系统串联。没有 effect + track + trigger 三件套,它只是个会打日志的哑巴代理。











