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.

1
  • 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. Commented Jun 10, 2017 at 19:14

2 Answers 2

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;
});
Sign up to request clarification or add additional context in comments.

1 Comment

Or, instead of a variable the closure scope, as a property on the handler object.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.