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 "retain" the Proxy
after an Object.assign()
(or an object spread operator, which afaik is just syntax sugar for Object.assign()
)?
let objProxyNew = Object.assign({}, objProxy); // i.e. {...objProxy};
So that objProxyNew.id
returns "id from proxy"
?
Seems like I'm the third person with the exact same problem and this is the closest question on stackoverflow I've found but it has no real answers so I had to investigate it by myself.
Accidentally the behavior that Philip wants in his example is the default behavior so no change is necessary:
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 works only for simple proxies where the names of properties on the proxied object is the same as for the final object.
{...obj}
for javascript proxy objectLet's take a little more complicated example, a proxy for "zip" operation (combining separate arrays of keys and values into a single 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}
Now we got properties from the original object, but no values for them because the proxy returns nothing for "keys" and "values" properties.
As I found out, this is happening because we haven't defined trap for "ownKeys" and Object.getOwnPropertyNames(target)
is called by default.
Extending proxy with:
ownKeys(target) { return target.keys; }
makes it even worse because no properties are cloned now at all:
console.log({...objProxy}); // {}
What is happening right now is that Object.assign calls Object.getOwnPropertyDescriptor
for every key returned by "ownKeys" function. By default property descriptors are retrieved from "target" but we can change it once again with another trap 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 will be cloned and visible in console.
configurable
must be set for proxied properties, otherwise we will get error:
VM1028:1 Uncaught TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property 'a' which is either non-existent or configurable in the proxy target at :1:1
we also need to set "writable" to "true" to be able to set property in strict mode.
value
seems to be not used by Object.assign
but may be used by some other framework or implementation. If it's costly to actually get a value, we can define it as a getter:
get value() { return this.get(target, name); }
To support in
operator and to have consistent implementation, we should also implement "has" trap. So the final implementation can 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}
[...obj]
for javascript proxy objectAnother story is to support [...objProxy]
- here, [Symbol.iterator]
is called which we need to define in a 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 proxy "Symbol.iterator" to original object:
return () => target.values[Symbol.iterator]();
or
return target.values[Symbol.iterator].bind(target.values);
we need to re-bind original context because otherwise iterator will be executed for Proxy object
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With