const handler = {};
for (const key of Reflect.ownKeys(Reflect))
handler[key] = (target, ...rest) => Reflect[key](target.deref(), ...rest);
const proxy = value => {
const wr = new WeakRef(value);
const proxy = new Proxy(wr, handler);
return proxy;
};
I know not all own things in the Reflect namespace are functions, but that’s not the issue (and I don’t care for code brevity sake) but basically I had to fallback to this:
for (const key of Reflect.ownKeys(Reflect)) {
handler[key] = key === 'set' ?
// proxy as receiver is trouble with set
((target, prop, value) => Reflect.set(target.deref(), prop, value)) :
((target, ...rest) => Reflect[key](target.deref(), ...rest))
;
}
why is that, if I might ask? why all Reflect methods would just work but set in particular would cause me troubles?
P.S. the workaround works like a charm, I just don’t like not understanding why I need workarounds in the first place, it’s been years using proxies and this one in particular caught me by surprise, thanks.
edit to reproduce the issue just proxy(document.body).textContent = 123 to see the error
The fourth argument to the set handler is the receiver. You’re trying to invoke the textContent setter (it’s a setter, not just a property) with a this value which is not a DOM node.
Compare:
const handler = {};
for (const key of Reflect.ownKeys(Reflect))
handler[key] = (target, ...rest) => Reflect[key](target.deref(), ...rest);
const proxy = value => {
const wr = new WeakRef(value);
const proxy = new Proxy(wr, handler);
return proxy;
};
let object = {
set foo(v) {
if (this !== object) throw new Error('bad receiver');
return;
}
}
proxy(object).foo = 123 // throws
so … if I understand correctly, the set fails because it’s a setter and that’s expected?
what I would expect, is because also all getPropertyDescriptor traps pass through the very same proxy, the receiver would just “automagically” do that dance for me … how wrong am I? (as this seems to be a desired behavior, feels like a lot!)
what would be a good use case for the receiver then, if not just by failing out of the blue as the only Reflect method that has different expectations?
if (this !== object) throw new Error('bad receiver');
that looks to me like the setter should be aware of the reference being eventually proxied … which is a leak in implementation around proxies expectations … so once again: what is that receiver about? I remember it helping with some prototype proxied trap long time ago, yet I am not sure that’s what users would write/run/do out there … is the fact a prototype can be a proxy the only reason the receiver exist? and why just set then, but not get ?
edit never mind, I’ve just realized get might have the same issue
receiver is exactly what it says, it's the receiver of the get / set operation, which can be different than the current target of an operation in the case of proxies, if the target is on the prototype chain, or if Reflect methods are called with an explicit receiver.
For accessors, it becomes the this value. For ordinary objects, when setting a property that doesn't exist, it's actually the built-in behavior of the root object in the prototype chain that defines the property on the receiver (see OrdinarySetWithOwnDescriptor).
In the case of proxies, the proxy instance is the receiver. Proxies fully defer to their handler on how to treat the receiver, as some usages of proxies attempt to intercept the internal behavior which requires maintaining the proxy object as receiver, which will break any kind of identity based checks by the proxied object. This is why you often hear people complain that proxies break private fields.
The best way to have proxies not break their targets is by making sure the target isn't aware of the proxy's existence, by mapping the receiver in get/set to the target, but also by returning "mapped functions" for any result of get so that calling proxy.method() doesn't end up revealing proxy as this for method invocations.
Membranes do this dynamically by creating proxies for all non-primitive values returned by proxies, and maintaining a bidirectional mapping from targets to proxies.
I swear I read binding before and because that’s a delicate topic I’d like to explain to future readers how not to bind methods because:
obj.method === obj.method should always be true
obj.method inherited from a prototype should not bloat RAM by creating, per each object, a bound version of that inherited method
const { apply, get, set } = Reflect;
const methods = new WeakMap;
const proxies = new WeakMap;
const methodHandler = {
apply: (target, self, args) => apply(
target,
// proxy as context -> real target
proxies.get(self) ?? self,
args,
),
};
const sourceHandler = {
get(target, key, proxy) {
let value = get(target, key);
if (typeof value === 'function') {
if (!proxies.has(proxy)) {
// remember the real target
proxies.set(proxy, target);
}
if (!methods.has(value)) {
// wrap this function as a proxy *once*
methods.set(value, new Proxy(value, methodHandler));
}
value = methods.get(value);
}
return value;
},
set: (target, key, value) => set(target, key, value),
};
const wrap = value => new Proxy(value, sourceHandler);
// example
wrap(document.body).append('OK');
edit
as any context could be also a primitive, a guard around (typeof self === ‘object‘ && self) || typeof object === ‘function’ might be needed before passing proxies.get(self) ?? self or just self instead, for more convoluted / unknown scenarios.