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"
?
source to share
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"
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}
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]
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
source to share