1

I'm attempting to create multiple Proxy wrappers for the same target object in JavaScript, with each individual wrapper having slightly different properties which affect how the wrapped functionality operates. These properties are assigned to and accessed from the receiver object in the set and get handlers. However, when I examine the generated Proxies, all of them have the property set I expect to have been assigned to the last Proxy created.

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);

My expected result: [{name: 'one'}, {name: 'two'}].

The actual result: [{name: 'two'}, {name: 'two'}]. Even though they appear identical, they are not strictly equal.

If I omit const obj and create my objects with new Proxy({}, ...) I get the expected result -- a proxy one and a proxy two, presumably since the target reference is not shared between them. So: what on earth? From my understanding, using the receiver to store name should prevent it from being propagated to the target object, yet it seems to be doing so anyway.

dmfay
  • 2,417
  • 1
  • 11
  • 22
  • Where exactly do you expect the values to be stored, what do you expect `receiver` to be? Hint: you cannot store anything on a proxy itself. – Bergi Jun 10 '17 at 19:14

2 Answers2

2

Your snippet

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

isn't going to do what (I think) you expect it to do. Since receiver here is the proxy object, the property definition will also be proxied through to target, meaning that the distinction between the branches in your if/else is almost nothing. If you're looking to store a unique name for each proxy object, the easiest thing to do in this case would be to use the closure's scope, e.g.

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;
});
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
0

This seems to happen when setting properties directly on the Proxy. The behavior is unrelated to instantiating multiple Proxies; creating a single Proxy and setting its name also pollutes the target.

Using an inheriting object with its prototype set to the proxy as detailed in this answer to a related question does not pollute the proxy's target.

dmfay
  • 2,417
  • 1
  • 11
  • 22