我需要在页面上劫持XHR,我从代理XHR对象开始,第一个代理new
运算符:
class ClassHandler {
constructor(proxy) {
this.proxy = proxy;
}
construct(target, args) {
const obj = new target(...args);
return new Proxy(obj, new this.proxy(obj));
}
}
(function(XMLHttpRequest) {
unsafeWindow.XMLHttpRequest = new Proxy(
XMLHttpRequest,
new ClassHandler(XhrHandler)
);
})(XMLHttpRequest);
然后XhrProxy
需要将所有内容都代理到实际的XHR对象,我发现这很复杂,因此请将其分为几个部分。
const ProxyGetTarget = Symbol('ProxyGetTarget');
const ProxyGetHandler = Symbol('ProxyGetHandler');
class ObjectHandler {
constructor(target) {
this.target = target;
}
get(target, prop, receiver) {
if (target.hasOwnProperty(prop)) {
return Reflect.get(target, prop, receiver);
} else if (prop == ProxyGetTarget) {
return target;
} else if (prop == ProxyGetHandler) {
return this;
} else {
const value = target[prop];
if (typeof value == 'function')
return new Proxy(value, new FunctionHandler(value));
return value;
}
}
set(target, prop, value) {
return Reflect.set(target, prop, value);
}
}
我注入了属性ProxyGetTarget
,该属性返回了实际目标以备将来使用。接下来,我将解释为什么代理在对象中使用FunctionHandler
的功能。
在对象上调用函数时,这有点棘手:
xhr.open()
xhr
是我们的代理对象,不是真正的本机XHR对象,将在this
设置为代理对象的情况下调用open函数,为了将open调用转发到实际的xhr对象,我们需要代理get
返回的所有函数:
class FunctionHandlerBase extends ObjectHandler {
apply(target, thisArg, argumentsList) {
const realTarget = thisArg[ProxyGetTarget];
if (!realTarget) throw new Error('illegal invocations');
return this.call(this.target, thisArg, realTarget, argumentsList);
}
}
class FunctionHandler extends FunctionHandlerBase {
call(fn, proxy, target, argumentsList) {
fn.apply(target, argumentsList);
}
}
通过此设置,我可以使用以下方法将任何代码注入到任何成员函数中:
class XhrHandler extends ObjectHandler{
get(target, prop, receiver) {
if (prop === 'open') {
return new Proxy(target.open, new this.open(target.open));
} else {
return super.get(target, prop, receiver);
}
}
}
XhrHandler.prototype.open = class extends FunctionHandlerBase {
call(fn, proxy, realTarget, argumentsList) {
// Do whatever before real xhr.open call
// ...
// ...
// Call real xhr.open
return fn.apply(realTarget, argumentsList);
}
};
由于事件侦听器将在回调函数中将真实对象公开为this
参数,所以我不希望任何人获得我的秘密真实目标,我也应该将其包装在代理中。
class EventTargetHandler extends ObjectHandler {
constructor(target) {
super(target);
this.listeners = {};
}
getListeners(type) {
if (!this.listeners.hasOwnProperty(type))
this.listeners[type] = new Map();
return this.listeners[type];
}
get(target, prop, receiver) {
if (prop === 'addEventListener') {
return new Proxy(
target.addEventListener,
new this.addEventListener(target.addEventListener, this)
);
} else if (prop === 'removeEventListener') {
return new Proxy(
target.removeEventListener,
new this.removeEventListener(target.removeEventListener, this)
);
} else return super.get(target, prop, receiver);
}
}
EventTargetHandler.prototype.addEventListener = class extends FunctionHandlerBase {
call(fn, proxy, realTarget, argumentsList) {
const type = argumentsList[0];
const listener = argumentsList[1];
const bridge = listener.bind(proxy);
argumentsList[1] = bridge;
proxy[ProxyGetHandler].getListeners(type).set(listener, bridge);
return fn.apply(realTarget, argumentsList);
}
};
EventTargetHandler.prototype.removeEventListener = class extends FunctionHandlerBase {
call(fn, proxy, realTarget, argumentsList) {
const type = argumentsList[0];
const listener = argumentsList[1];
const cache = proxy[ProxyGetHandler].getListeners(type);
if (cache.has(listener)) {
argumentsList[1] = cache.get(listener);
cache.delete(listener);
}
return fn.apply(realTarget, argumentsList);
}
};
还没有完成...我需要将所有set
包装到事件处理程序属性中。
嗯,劫持XHR吗?简单的任务,让我们在5分钟内完成。
...
...
...
5小时后,我仍然在与这些代理作斗争。
也许我误解了Proxy
API的设计提议,但发现包装一个对象非常复杂,几乎是不可能的。
我只希望代理的行为与原始对象相同,有没有更简单的方法?