我有一个实现XMLHttpRequest接口的类。根据传递给open()
的URL,我可以确定是使用默认的XMLHttpRequest还是我的自定义实现。我的想法是使用代理来执行此操作:
let xhr = new XHRProxy();
xhr.open('GET', 'http://blah'); // Decide here depending on URL
我使用ES6代理进行了一些测试,看起来很有希望,但不幸的是,构建代理后无法修改代理目标:
var foo = {
name() {
return "foo";
}
};
var bar = {
name() {
return "bar";
}
}
var handler = {
get(target, property, receiver) {
if (property === "switchToBar") {
// FIXME: This doesn't work because a Proxy's target is not exposed AFAIK
receiver.target = bar;
return function() {};
} else {
return target[property];
}
}
}
var proxy = new Proxy(foo, handler);
console.log(proxy.name()); // foo
proxy.switchToBar();
console.log(proxy.name()); // foo :(
我想我可以通过不设置目标来完成我想要的 - 而是定义所有陷阱以委托给所需的对象 - 但我希望有一个更简单的解决方案。
这里是“定义委托给所需对象的所有陷阱”
(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()}`);
我觉得必须有一些理由不支持开箱即用,但我没有足够的信息来进一步评论。
经过一段时间的黑客攻击后,我发现了一些事情。无论如何,代理构造函数调用的原始目标似乎都被视为代理身份的一部分。在调用代理时,将原始目标设置为普通对象并将目标交换到函数稍后会引发错误。这肯定有一些粗糙的边缘,所以请谨慎使用。
是否可以更改代理的目标?
不,这是不可能的。代理处理程序已经是一个非常通用的接口,通过定义所有陷阱来将操作转发到不同的处理程序,这很容易实现。这就是没有额外方法来改变目标的原因,界面保持最小化。通过不使目标可变,也保留代理的形状(例如,它是可调用的还是数组)。
这个怎么样?我们不是直接将foo
或bar
作为目标,而是使用一个盒子作为目标,然后将foo
或bar
放入盒子中。
var foo = {
name() {
return "foo";
}
};
var bar = {
name() {
return "bar";
}
};
var handler = {
get(target, property, receiver) {
if (property === "switchToBar") {
target.content = bar;
return function() {};
} else {
return target.content[property];
}
}
};
var box = {content: foo};
var proxy = new Proxy(box, handler);
console.log(proxy.name()); // foo
// Switch over to bar by calling the function
proxy.switchToBar();
// Or, we could do the switch from out here
box.content = bar;
// Either way, we get the same result
console.log(proxy.name()); // bar
在这种情况下,我们的框是一个具有属性content
的对象。但另外,您可以使用索引为0的项目的数组。
我接受了John的回答并对其进行了改进(我希望):
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]);
}
}),
)
});
一些重要的区别:
mutableTarget/Handler
的所有结果共享一个mutableProxyFactory
变量,因此您可能无法多次调用它。this
绑定到处理程序,就像在正常的Proxy
中一样。mutableProxyFactory
未在全球范围内分配给window
。