Proxy能够监听到对象中的对象的引用吗?

简短结论

原生的 new Proxy(target, handler)只能代理「它直接包裹的那一层对象」 ,对 target内部的嵌套对象,默认是"透传"的------返回的是裸对象,后续操作完全逃逸监听。


为什么会"听不到"嵌套对象?

复制代码
const obj = { a: { b: 1 } };
const proxy = new Proxy(obj, {
  get(t, k) { console.log('get', k); return Reflect.get(t, k); },
  set(t, k, v) { console.log('set', k, v); return Reflect.set(t, k, v); }
});

proxy.a.b = 99;
// 只触发了一次 get(a),返回的是原始裸对象 { b: 1 }
// set(b) 永远不会触发!

执行 proxy.a.b = 99的过程:

  1. 先走 get(proxy, 'a')→ 拿到 obj.a(原始 { b: 1 },不是 Proxy)

  2. 然后对这个裸对象 执行 .b = 99→ 跟 Proxy 毫无关系

所以不是 Proxy "能力不够",而是它根本没有机会介入第二步------因为第一步返回的就不是代理对象。


✅ 解法:递归代理(Proxy Membrane 模式)

核心思路:在 get拦截器中,凡是读到的值是对象,就再给它套一层 Proxy,让整条访问链上的每一层都是代理过的:

复制代码
function deepProxy(target, handler) {
  // 缓存,避免重复代理 & 处理循环引用
  const cache = new WeakMap();

  function makeProxy(obj) {
    if (obj === null || typeof obj !== 'object') return obj;
    if (cache.has(obj)) return cache.get(obj);

    const proxy = new Proxy(obj, {
      get(t, key, receiver) {
        const val = Reflect.get(t, key, receiver);
        // 读到子对象 → 递归代理后返回
        return (val !== null && typeof val === 'object')
          ? makeProxy(val)
          : val;
      },
      set(t, key, value, receiver) {
        const oldVal = t[key];
        const result = Reflect.set(t, key, value, receiver);
        handler?.onChange?.({
          type: 'SET',
          path: key,
          oldValue: oldVal,
          newValue: value
        });
        return result;
      },
      deleteProperty(t, key) {
        const had = key in t;
        const oldVal = t[key];
        const result = Reflect.deleteProperty(t, key);
        if (had) handler?.onChange?.({ type: 'DELETE', path: key, oldValue: oldVal });
        return result;
      }
    });

    cache.set(obj, proxy);
    return proxy;
  }

  return makeProxy(target);
}

使用效果:

复制代码
const state = deepProxy({ a: { b: 1 }, list: [10, 20] }, {
  onChange: ({ type, path, oldValue, newValue }) =>
    console.log(`[${type}] ${path}:`, oldValue, '→', newValue)
});

state.a.b = 99;       // ✅ 能捕获!(经过递归代理的 a 的 set 触发)
state.list.push(30);   // ⚠️ 数组的 push 本质是方法调用,set trap 不一定按你想的方式触发
state.a = { c: 2 };    // ✅ 外层 set 正常捕获(替换整个子对象引用)

两种"引用变化"要区分清楚

场景 能否被外层 Proxy 的 set捕获? 说明
proxy.a = { c: 2 }(替换整个子对象引用) ✅ 能​ 这是 proxy 自身的属性赋值,走 set(proxy, 'a', ...)
proxy.a.b = 99(修改子对象内部属性) ❌ 不能(除非递归代理) 操作的是子对象,外层 proxy 根本碰不到
proxy.a = proxy.a(把子对象重新赋回) ✅ 能触发 set 虽然值没变但赋值行为本身被拦截

⚠️ 几个容易踩的坑

  1. 数组的 push、pop等方法 :它们内部会读写 length,走的是方法调用路径而非简单 set,做响应式系统时通常需要额外处理(Array的陷阱更复杂,Vue 3 用的也不是纯递归 Proxy 这么简单)

  2. 必须用 receiver传进 Reflect.get :如果对象上有 getter 或原型链继承,漏掉 receiver 会导致 this指向错误:

    复制代码
    // ✅ 正确
    const val = Reflect.get(t, key, receiver);
    // ❌ 危险
    const val = t[key];
  3. typeof null === 'object' ​ → 判断时一定要加 && value !== null

  4. 性能 :每次 get都判断+可能创建 Proxy,不加缓存的话同个引用被访问 N 次就产生 N 个 Proxy 实例。用 WeakMap做缓存是标准做法


一句话总结

Proxy 本身是"单层"的------它只看守你交给它的那扇门。 ​ 想监听对象中的对象,就得在 get里把每个子对象也变成 Proxy (即 Proxy Membrane / 深代理),这也就是 Vue 3 的 reactive()背后的核心思想。Proxy 不是不能,是需要你主动递归地"铺网"。

相关推荐
hold?fish:palm6 分钟前
44 二叉搜索树中第K小的元素
开发语言·c++·算法
凉茶钱21 分钟前
【吃透C++】万字解析类和对象
开发语言·c++
data analyse 45629 分钟前
报表能自定义的国内统计工具有哪些
前端·数据分析
白杨尚青43 分钟前
C++入门篇(十):string(上)——认识string:构造与三大遍历(一条龙讲透operator[]、迭代器、auto、范围for)
java·开发语言·c++·笔记·stl
IT_陈寒1 小时前
JavaScript闭包的这个坑,我居然今天才爬出来
前端·人工智能·后端
可乐鸡翅yeah_1 小时前
hls.js 切换多个视频源,新手开发常见踩坑
开发语言·前端·javascript·ios·ffmpeg·音视频·safari
Tanshu_API君1 小时前
API 聚合平台选型指南:从参数拆解到生产落地的十个评估维度
开发语言·api
Chen—LSN1 小时前
C语言——⽂件操作(1)
c语言·开发语言·c++·经验分享·笔记·算法·链表
葡萄城技术团队1 小时前
复制走的是数字,留下的是身份:Web 表格里被忽视的数据断层
前端