同一目标的多个代理对象的明显污染

Apparent pollution with multiple Proxy objects for the same target

我正在尝试为 JavaScript 中的同一个目标对象创建多个代理包装器,每个包装器的属性略有不同,这些属性会影响包装功能的运行方式。这些属性被分配给 receiver 对象并从 setget 处理程序中访问。但是,当我检查生成的代理时,它们都具有 属性 集,我希望它们已分配给创建的 last 代理。

const obj = {};

const proxies = ['one', 'two'].map(name => {
  console.log(`proxy ${name}`);

  const proxy = new Proxy(obj, {
    get: (target, prop, receiver) => {
      if (prop === 'name') { return receiver.name; }

      return target[prop];
    },
    set: (target, prop, val, receiver) => {
      if (prop === 'name') {
        console.log(`setting name ${val} on receiver`);
        Object.defineProperty(receiver, prop, {
            value: val,
            configurable: true,
            enumerable: true}
        );
      } else {
        console.log(`setting ${prop} ${val} on target`);
        target[prop] = val;
      }

      return true;
    }
  });

  proxy.name = name;

  return proxy;
});

console.log();
console.log(proxies);

我的预期结果:[{name: 'one'}, {name: 'two'}].

实际结果:[{name: 'two'}, {name: 'two'}]。尽管它们看起来相同,但它们并非严格相等。

如果我省略 const obj 并使用 new Proxy({}, ...) 创建我的对象,我会得到预期的结果——一个代理 one 和一个代理 two,大概是因为目标它们之间不共享引用。那么:究竟是什么?根据我的理解,使用 receiver 存储 name 应该可以防止它传播到目标对象,但它似乎还是这样做了。

直接在代理上设置属性时似乎会发生这种情况。该行为与实例化多个代理无关;创建单个代理并设置其 name 也会污染目标。

如相关问题 this answer 中所述,使用其原型设置为代理的继承对象不会污染代理的目标。

您的代码段

Object.defineProperty(receiver, prop, {
    value: val,
    configurable: true,
    enumerable: true}
);

不会做(我认为)您期望它做的事情。由于这里的receiver是代理对象,所以属性的定义也会被代理到target,也就是说你的if/else中的分支之间的区别几乎没有。如果您希望为每个代理对象存储一个唯一的名称,那么在这种情况下最简单的做法就是使用闭包的作用域,例如

const proxies = ['one', 'two'].map(name => {
  console.log(`proxy ${name}`);

  const proxy = new Proxy(obj, {
    get: (target, prop, receiver) => {
      if (prop === 'name') { return name; }

      return Reflect.get(target, prop, receiver);
    },
    set: (target, prop, val, receiver) => {
      if (prop === 'name') {
        name = val;
        return true;
      }

      return Reflect.set(target, prop, val, receiver);
    },
    ownKeys: (target) => {
      return Reflect.ownKeys(target).concat('name');
    },
    getOwnPropertyDescriptor: (target, prop) => {
      if (prop === "name") return { enumerable: true, writable: true, configurable: true, value: name };

      return Reflect.getOwnPropertyDescriptor(target, prop);
    },
  });

  return proxy;
});