Object.assign and proxies

Having the following object:

let obj = { id: 0 };

      

and the following Proxy

:

let objProxy = new Proxy(obj, {
  get: (target, name) => {
    if (name == "id")
      return "id from proxy";
}});

      

Is it possible to "save" Proxy

after Object.assign()

(or an object spread operator, which afaik is just syntactic sugar for Object.assign()

)?

let objProxyNew = Object.assign({}, objProxy); // i.e. {...objProxy};

      

So it objProxyNew.id

returns "id from proxy"

?

+4


source to share


1 answer


It looks like I'm a third person with exactly the same problem and this is the closest stack thread question I have found, but there are no real answers to it, so I had to research it myself.

The random behavior that Philip wants in his example is the default behavior, so no changes are required:

let obj = { id: 0 };
let objProxy = new Proxy(obj, {
  get: (target, name) => {
    if (name == "id")
      return "id from proxy";
}});
    
let objProxyNew = Object.assign({}, objProxy);

console.log(objProxyNew.id); // "id from proxy"
      

Run codeHide result


But this only works for simple proxies where the property names in the proxy object are the same as for the target object.

Implementation {...obj}

for javascript proxy object

Let's look at a more complex example - a proxy for the "zip" operation (combining separate arrays of keys and values ​​into one object):

let objProxy = new Proxy({
    keys: ["a", "b", "c", "d"],
    values: [1, 3, 5, 7]
}, {
    get(target, name) {
        var index = target.keys.indexOf(name);
        return index >= 0 ? target.values[target.keys.indexOf(name)] : false
    }
});

console.log(objProxy.c); // 5   

console.log({...objProxy}); // {keys: undefined, values: undefined}

      

We now have the properties of the original object, but no values ​​for them, because the proxy does not return anything for the "keys" and "values" properties.

As I found out, this is because we have not defined a hook for "ownKeys" and Object.getOwnPropertyNames(target)

is called by default.

Extending a proxy with

    ownKeys(target) { return target.keys; }

      

makes it even worse because no properties are cloned at all:

console.log({...objProxy}); // {}

      

What is happening now is what Object.assign calls Object.getOwnPropertyDescriptor

for each key returned by the ownKeys function. By default, property descriptors are fetched from "target", but we can change it again with another hook called "getOwnPropertyDescriptor":

let objProxy = new Proxy({
    keys: ["a", "b", "c", "d"],
    values: [1, 3, 5, 7]
}, {
    get(target, name) {
        var index = target.keys.indexOf(name);
        return index >= 0 ? target.values[index] : false
    },
    ownKeys(target) {
        return target.keys;
    },
    getOwnPropertyDescriptor(target, name) {
        return { value: this.get(target, name), configurable: true, enumerable: true };
    }
});

      



enumerable

controls which properties are cloned and visible in the console. configurable

must be set for proxy properties, otherwise we will get an error:

VM1028: 1 unhandled error like: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurable for property 'a' which either does not exist or is configured in proxy target at: 1: 1

we also need to set "writable" to "true" to be able to set properties in strict mode.

value

is apparently not used Object.assign

, but might be used in some other environment or implementation. If getting the value is really expensive, we can define it as the receiver:

    get value() { return this.get(target, name); }

      

To support the operator in

and have a consistent implementation, we must also implement the has hook. So the final implementation might look like this:

let objProxy = new Proxy({
	keys: ["a", "b", "c", "d"],
	values: [1, 3, 5, 7]
}, {
	get(target, name) {
		var index = target.keys.indexOf(name);
		return index >= 0 ? target.values[index] : false
	},
	ownKeys: (target) => target.keys,
	getOwnPropertyDescriptor(target, name) {
		const proxy = this;
		return { get value() { return proxy.get(target, name); }, configurable: true, enumerable: true };
	},
	has: (target, name) => target.keys.indexOf(name) >= 0
});

console.log({...objProxy}); // {a: 1, b: 3, c: 5, d: 7}
      

Run codeHide result


Implementation [...obj]

for javascript proxy object

Another story is support [...objProxy]

- here is called [Symbol.iterator]

, which we have to define in the getter:

let objProxy = new Proxy({
	values: [1, 2, 3, 4],
	delta: [9, 8, 7, 6]
}, {
	get(target, name){
		if (name === Symbol.iterator) {
			return function*() {
				for (let i = 0; i < target.values.length; i ++) { yield target.values[i] + target.delta[i]; }
			}
		}
		return target.values[name] + target.delta[name];
	}
});

console.log([...objProxy]); // [10, 10, 10, 10]
      

Run codeHide result


we can also just pass "Symbol.iterator" to the original object:

return () => target.values[Symbol.iterator]();

      

or

return target.values[Symbol.iterator].bind(target.values);

      

we need to re-bind the original context because otherwise the iterator will be executed for the proxy object

0


source







All Articles