在实际使用中有一个奇怪的需求。我需要劫持XMLHttpRequest.prototype.open
来监控全部的请求内容。
以往常用的劫持方法是这样的
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
// do something;
originalOpen.apply(this, arguments);
}
这样做确实是可以做到,但是存在一个问题:如果 XMLHttpRequest.prototype.open
被第三者重新覆盖掉了,我们就没法监视方法的执行了。
很自然想到去监视这个“覆盖”操作,在被覆盖的时候往别人身上插点代码,但是研究了半天,想要实现这个有一点问题。
监控对象的属性被赋值,可以使用Proxy
。但是XMLHttpRequest.prototype
是writable: false, configurable: false
的,覆盖它并无效果。
那么我们还有另一种方法就是使用 setter 来监视赋值操作。
我们能不能给XMLHttpRequest.prototype.open
新建一个setter在不影响它原来值的情况下监视它被赋值呢,答案是不可以。我们需要先备份原值。
XMLHttpRequest.prototype._open = XMLHttpRequest.prototype.open;
为了防止自我调用无限循环,使用 _open 来存储原来的值。同时定义一个新的 getter 重定向对原 open 的调用。
监视到赋值操作之后就需要在盖上来的东西上挂点自己的东西,最终我们还是使用 Proxy
的handler.apply
来劫持函数的“执行”这一动作。handler.apply
第一个参数是被执行的函数,第二个参数是callee
,为不影响其原有功能,用Function.prototype.call
来手动指定它的this
,第三参数为调用 f 时传入的参数的数组。注意是数组……传进函数的时候注意要展开,坑了我好一会儿(因为是在实际应用环境下操作的,有点复杂不好调试)。
Object.defineProperty(XMLHttpRequest.prototype, 'open', {
get: function () {
return this._open;
},
set: function (f) {
this._open = new Proxy(f, {
apply: function(f, instance, fargs) {
listen.call(instance, ...fargs);
return f.call(instance, ...fargs)
}
});
}
});
最终我们得到类似于上面的代码。在不影响原来函数功能的情况下插入了一段自己的listen
代码,我好了。
kktql