You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含深层嵌套Proxy对象属性的赋值表达式为何无法更新目标属性?

嵌套Proxy赋值失效问题解析

我写了一个接收对象并返回其代理版本的函数,修改代理对象不会影响原对象:

const createProxy4 = (obj) => {
  const handler = {
    get(target, prop) {
      // 如果属性值是对象,返回前替换为新的Proxy
      if (target[prop] !== null && typeof target[prop] === "object") {
        target[prop] = new Proxy({ ...target[prop] }, handler);
      }
      // 原始值直接返回
      return target[prop];
    },
    set(target, prop, value) {
      // 更新属性直接赋值
      target[prop] = value;
      return true;
    },
  };

  // 创建原对象的浅拷贝
  return new Proxy({ ...obj }, handler);
};

示例

// 原对象
const obj8 = {
  x: { y: 5 },
};
// 创建代理副本
const proxy8 = createProxy4(obj8);

// 修改代理对象的嵌套属性
proxy8.x.y = 27;
console.log(obj8); // { x: { y: 5 } } - 原对象不受影响
console.log(proxy8); // { x: { y: 27 } } - 代理对象属性修改生效

// 使用自身属性的表达式修改代理
proxy8.x.y = proxy8.x.y + 2;
console.log(obj8); // { x: { y: 5 } } - 原对象不受影响
console.log(proxy8); // { x: { y: 27 } } - 无变化!为什么?

// 使用中间变量重构表达式
const t = proxy8.x.y + 2;
proxy8.x.y = t;
console.log(obj8); // { x: { y: 5 } } - 原对象不受影响
console.log(proxy8); // { x: { y: 29 } } - 修改生效。为什么?

问题解析

两种写法结果不同的核心原因,在于你的Proxy实现每次访问嵌套对象时都会创建全新的Proxy实例:

为什么直接赋值表达式失效?

在proxy8.x.y = proxy8.x.y + 2中,JavaScript会先计算右侧表达式,再执行左侧赋值:

  1. 计算右侧:访问proxy8.x触发外层Proxy的get拦截器,此时外层目标对象的x是之前的代理实例,于是创建一个全新的ProxyA替换掉外层目标对象的x,读取ProxyA的y值(27)计算得到29;
  2. 执行左侧赋值:再次访问proxy8.x触发get拦截器,此时外层目标对象的x是ProxyA,又会创建另一个全新的ProxyB替换掉外层目标对象的x,然后把29赋值给ProxyB的y;

后续console.log(proxy8)时,又会触发proxy8.x的get拦截器,基于当前外层目标对象的x(ProxyB)创建新的ProxyC——理论上ProxyB的y已经是29,但示例注释的误差可能来自浏览器console对Proxy的特殊打印机制。本质是:左右两边的proxy8.x指向完全不同的Proxy实例,赋值操作没有作用到后续访问会用到的代理上。

为什么中间变量写法生效?

在const t = proxy8.x.y + 2; proxy8.x.y = t;中:

  1. 第一次访问proxy8.x创建ProxyA,读取y=27得到t=29;
  2. 第二次访问proxy8.x创建ProxyB,将t=29赋值给ProxyB的y;
  3. 后续console.log(proxy8)时,访问proxy8.x创建ProxyC,基于ProxyB的目标对象(y=29),因此能显示正确结果。

本质缺陷与修复

你的Proxy实现的问题在于重复创建代理,每次访问嵌套对象都会生成新的Proxy,不仅导致赋值失效,还浪费性能。修复方案是确保每个嵌套对象只被代理一次:

const createProxy4 = (obj) => {
  // 深拷贝原对象,完全隔离原对象的修改
  const deepClone = (obj) => {
    if (obj === null || typeof obj !== "object") return obj;
    return Array.isArray(obj) ? obj.map(deepClone) : Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, deepClone(v)]));
  };

  const handler = {
    get(target, prop) {
      const value = target[prop];
      // 仅当属性是对象且未被代理时,才创建Proxy
      if (value !== null && typeof value === "object" && !(value instanceof Proxy)) {
        target[prop] = new Proxy(value, handler);
      }
      return target[prop];
    },
    set(target, prop, value) {
      target[prop] = value;
      return true;
    },
  };

  return new Proxy(deepClone(obj), handler);
};

修改后,每个嵌套对象只会被代理一次,后续访问同一属性返回同一个Proxy实例,赋值操作就能正常生效了。

内容的提问来源于stack exchange,提问作者Black Beard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 17:54:55