Proxy需在get中用Reflect.get()确保正确处理getter、this绑定及Symbol等边界;为防无限递归和内存泄漏,须用WeakMap缓存代理对象并过滤原生类型;set中仅触发通知而非直接操作DOM,更新逻辑交由外部effect处理。

Proxy 本身不自动递归代理嵌套对象,直接用 new Proxy(obj, handler) 只能拦截顶层属性的读写。想支持深度嵌套监听,必须在 get 拦截器里对返回值做“惰性代理”——即每次取值时,若结果是对象且未被代理过,就立刻用同一套 handler 再包一层。
为什么 get 中必须用 Reflect.get() 而不能直接 target[key]
直接访问 target[key] 会绕过原型链上的 getter、丢失 this 绑定(尤其在 class 实例或有 accessor 的对象上),还可能触发意外副作用。而 Reflect.get(target, key, receiver) 是语言内置的标准行为,能正确处理所有边界情况:
-
receiver参数确保代理对象作为this传入 getter 函数 - 兼容 Symbol 属性、原型属性、计算属性名
- 与
set中的Reflect.set()行为对称,避免逻辑割裂
如何避免无限递归代理和内存泄漏
深度代理容易在循环引用对象(如父子节点、图结构)中触发死递归,或持续新建 Proxy 实例导致内存暴涨。关键防护点:
安装并运行 Aperture(Lightning Labs 的 L402 闪电反向代理),用于创建 L402 付费墙、配置付费 API 端点、为其他代理托管付费内容或测试 L402 认证流程。
- 用
WeakMap缓存已代理的对象,每次get前先查缓存:if (proxied.has(raw)) return proxied.get(raw) - 只对 plain object 和数组做递归代理,跳过
Date、RegExp、Map、Set等原生类型(它们通常不该被响应式化) - 在
get中加类型判断:if (value && typeof value === 'object' && !isRaw(value)),其中isRaw可标记原始对象(如通过Object.isFrozen()或自定义$$raw属性)
set 拦截后必须手动触发更新,但别在 handler 里写具体 DOM 操作
响应式引擎要解耦数据层和视图层。正确的做法是:在 set 中只做两件事——调用 Reflect.set() 完成赋值,并通知订阅者;具体怎么更新 DOM,应由上层框架或用户注册的 effect 函数决定:
立即学习“前端免费学习笔记(深入)”;
- 不要在 handler 里写
document.getElementById(...).innerText = ... - 应该触发类似
trigger(key, oldValue, newValue)的函数,让外部 effect 重新执行渲染逻辑 - 如果值是新对象,还要递归调用
reactive()并把新 proxy 存入缓存,否则后续修改不会被监听
真正难的不是写通一个嵌套代理,而是控制代理粒度——比如数组索引赋值 arr[0] = x、push()、length 修改,这些操作默认不会进 set,得靠 ownKeys + getOwnPropertyDescriptor + 特殊方法重写才能覆盖全。轻量级引擎往往选择“只保核心路径”,把复杂场景留给用户显式调用 trigger 或换用 ref 封装。

















