JavaScript中Proxy实现深层响应式的核心是递归代理:在get/set拦截中对嵌套对象自动创建新Proxy,并用WeakMap缓存避免重复代理和循环引用,从而确保任意层级属性访问都被拦截。

JavaScript 中用 Proxy 实现深层嵌套代理与响应式观测,核心在于递归拦截所有嵌套属性的读写操作,并对新生成的对象自动包装为代理——不是只代理第一层,而是“访问即代理”。
基础 Proxy 代理单层对象
Proxy 本身只代理目标对象本身,不自动处理嵌套属性。例如:
let obj = { a: { b: 1 } };
let proxy = new Proxy(obj, { get(target, key) { console.log('get', key); return target[key]; } });
proxy.a.b; // 只触发 get('a'),但不会触发对 a.b 的拦截 —— 因为 a 是普通对象,没被代理。
要让 a.b 也被拦截,必须确保 a 本身也是个 Proxy。
递归代理:get 拦截中自动包装嵌套对象
在 get 拦截器中判断返回值是否为对象(且非 null、非数组/Date 等原生类型),如果是,就用同一套 handler 再次代理它:
立即学习“Java免费学习笔记(深入)”;
- 用 WeakMap 缓存已代理的对象,避免重复代理和内存泄漏
- 仅对 plain object 和数组做递归代理(跳过 Date、RegExp、Element 等)
- set 拦截中同样检查新值是否为对象,需要时也代理
示例关键逻辑:
const handlers = {
get(target, key, receiver) {
if (key === '__isProxy') return true;
const res = Reflect.get(target, key, receiver);
if (isObject(res) && !isPrimitive(res)) {
return reactive(res); // 递归代理
}
return res;
},
set(target, key, value, receiver) {
if (isObject(value) && !isPrimitive(value)) {
value = reactive(value);
}
const result = Reflect.set(target, key, value, receiver);
console.log(`set ${key} =`, value);
return result;
}
};
避免无限代理与循环引用
直接递归容易陷入死循环(比如对象包含自身引用)。安全做法:
- 用 WeakMap 记录原始对象 → 代理对象映射,每次 get 前先查缓存
- 在代理前判断是否已是代理(如加
__isProxy标记) - 对数组操作(push、pop 等)需额外拦截
ownKeys和has,或重写 Array.prototype 方法(Vue 3 采用后者)
WeakMap 缓存示例:
const toProxy = new WeakMap();
function reactive(obj) {
if (toProxy.has(obj)) return toProxy.get(obj);
if (!isObject(obj)) return obj;
const proxy = new Proxy(obj, handlers);
toProxy.set(obj, proxy);
return proxy;
}
配合依赖收集实现响应式更新
Proxy 本身只是拦截,要达成“响应式”,需结合依赖追踪机制:
- 每个响应式对象维护一个依赖集合(如 Set 存储 effect 函数)
- get 时收集当前正在执行的 effect(通过全局 activeEffect)
- set 时触发对应依赖重新执行
- 嵌套对象的 effect 应能被其任意层级变化触发(靠递归代理 + 统一依赖管理)
例如:effect(() => console.log(obj.a.b)),当 obj.a.b = 2 时,需触发该 effect。这要求 obj、obj.a、obj.a.b 的访问路径都参与依赖收集 —— 正是递归代理的价值所在。


















