Is it possible to change a Proxy's target?
Here's a go at "defining all traps to delegate to desired object"
(function () {
let mutableTarget;
let mutableHandler;
function setTarget(target) {
if (!(target instanceof Object)) {
throw new Error(`Target "${target}" is not an object`);
}
mutableTarget = target;
}
function setHandler(handler) {
Object.keys(handler).forEach(key => {
const value = handler[key];
if (typeof value !== 'function') {
throw new Error(`Trap "${key}: ${value}" is not a function`);
}
if (!Reflect[key]) {
throw new Error(`Trap "${key}: ${value}" is not a valid trap`);
}
});
mutableHandler = handler;
}
function mutableProxyFactory() {
setTarget(() => {});
setHandler(Reflect);
// Dynamically forward all the traps to the associated methods on the mutable handler
const handler = new Proxy({}, {
get(target, property) {
return (...args) => mutableHandler[property].apply(null, [mutableTarget, ...args.slice(1)]);
}
});
return {
setTarget,
setHandler,
getTarget() {
return mutableTarget;
},
getHandler() {
return mutableHandler;
},
proxy: new Proxy(mutableTarget, handler)
};
}
window.mutableProxyFactory = mutableProxyFactory;
})();
const {
proxy,
setTarget
} = mutableProxyFactory();
setTarget(() => 0);
console.log(`returns: ${proxy()}`);
setTarget({ val: 1 });
console.log(`val is: ${proxy.val}`);
setTarget({ val: 2 });
console.log(`val is: ${proxy.val}`);
setTarget(() => 3);
console.log(`returns: ${proxy()}`);
I feel like there must be some reason this isn't supported out of the box, but I don't have enough information to comment on that further.
After hacking on this for a while, I've observed a few things. It seems the original target that the proxy constructor is called with is treated as part of the proxy's identity regardless. Setting the original target to a plain object and swapping the target to a function later raises an error, when the proxy is called. There are definitely some rough edges to this, so use with caution.
Is it possible to change a Proxy's target?
No, this is not possible. The proxy handler is a quite generic interface already, and by defining all traps to forward the operation to a different handler this is easily achievable. That's why there is no extra method to change the target, the interface is kept minimal. By not making the target changeable, also the shape of the proxy is preserved (e.g. whether it's callable or an array).
I took John's answer and improved it (I hope):
const mutableProxyFactory = (mutableTarget, mutableHandler = Reflect) => ({
setTarget(target) {
new Proxy(target, {}); // test target validity
mutableTarget = target;
},
setHandler(handler) {
new Proxy({}, handler); // test handler validity
Object.keys(handler).forEach(key => {
const value = handler[key];
if (Reflect[key] && typeof value !== 'function') {
throw new Error(`Trap "${key}: ${value}" is not a function`);
}
});
mutableHandler = handler;
},
getTarget() {
return mutableTarget;
},
getHandler() {
return mutableHandler;
},
proxy: new Proxy(
mutableTarget,
new Proxy({}, {
// Dynamically forward all the traps to the associated methods on the mutable handler
get(target, property) {
return (_target, ...args) => mutableHandler[property].apply(mutableHandler, [mutableTarget, ...args]);
}
}),
)
});
Some important differences:
- John's version only has one
mutableTarget/Handler
variable shared by all results frommutableProxyFactory
, so you probably can't call it multiple times. - The handler is allowed to have additional keys, because there's no reason it shouldn't.
- Traps in the handler have
this
bound to the handler, as in a normalProxy
. - The user can give initial values for the handler and target. A value for the target is actually required.
mutableProxyFactory
isn't assigned globally towindow
.