proxy 拦截对象属性读写会失效,因其无法自动递归代理嵌套对象或数组,访问 obj.user.name 时 obj.user 返回原始对象,get 拦截器无法介入后续 .name 访问。

为什么直接用 Proxy 拦截对象属性读写会失效
因为 Proxy 只能代理对象本身,无法自动递归代理嵌套对象或数组——当你访问 obj.user.name 时,obj.user 返回的是原始对象,Proxy 的 get 拦截器没机会介入后续的 .name 访问。
常见错误现象:console.log(data.user.name) 触发了响应式更新,但 data.user.name = 'new' 却没触发视图更新,或者深层赋值后丢失响应性。
- 必须在每次
get返回值前判断是否为对象/数组,并返回其代理版本(递归代理) - 对
Array.prototype方法(如push、splice)需单独拦截,原生方法不会触发set - 避免重复代理:用 WeakMap 缓存已代理的对象,防止无限递归或内存泄漏
如何正确实现 get/set 拦截并触发更新函数
核心不是“监听变化”,而是“在读取时收集依赖,在写入时通知依赖”。get 中调用一个全局的 track 函数记录当前正在执行的副作用函数;set 中调用 trigger 执行所有关联函数。
典型使用场景:模板中 {{ user.name }} 渲染时执行 data.user.name,此时应把渲染函数存进 user.name 对应的依赖集合里。
-
get拦截器里必须调用track(target, key),且仅当存在活跃的副作用函数(如activeEffect)时才收集 -
set拦截器里先执行原赋值Reflect.set(),再调用trigger(target, key) - 注意
set必须返回布尔值(Reflect.set的返回值),否则严格模式下会报错TypeError: Cannot set property ... of #<object> which has only a getter</object>
数组索引赋值和 length 变更为何不触发 set
因为 arr[0] = x 和 arr.length = 0 都属于属性操作,会被 set 拦截——但问题常出在 Vue 或类似框架中手动重写了数组方法,而裸用 Proxy 时它们本该被拦截。真正容易漏的是:数组的 push、pop 等方法内部不走 set,而是直接改索引和 length,所以需要额外拦截这些方法。
- 代理数组时,要重写
target的原型,把push/pop/shift/unshift/splice/sort/reverse指向自定义函数 - 自定义
push中,先调用原生push,再手动触发trigger(target, 'length')和每个新增索引的trigger - 不要试图代理
length属性的set后再遍历删元素——用户可能直接赋值arr.length = 1,这时必须在set中识别key === 'length'并批量触发对应索引的更新
WeakMap 缓存代理对象时的几个硬坑
WeakMap 是必须的,但用错就会导致响应失效或内存泄漏。它只接受对象作键,且不阻止垃圾回收——这既是优点也是陷阱。
典型错误:把原始对象传给 reactive(),但内部用 new Proxy(obj, handler) 后没缓存,下次再调用又生成新代理,两个代理互不影响。
- 缓存键必须是原始对象(
target),不能是代理对象本身,否则 WeakMap 无法回收 - 代理后的对象如果被赋值给新变量(如
const a = reactive({x:1}); const b = a;),b和a是同一引用,没问题;但若const b = {...a},则b是普通对象,彻底脱离响应系统 - 对
Map/Set等内置集合类型,Proxy 无法直接拦截其迭代方法(如forEach),需用createInstrumentationGetter类似方案单独处理,一般项目可先忽略
最常被忽略的一点:Proxy 代理的是对象的「行为」,不是「身份」。只要原始对象还活着,代理就有效;一旦原始对象被 GC,WeakMap 里的缓存也自动消失——但你几乎没法主动控制这个时机,所以别在非必要时保留对原始对象的强引用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











