问题
今天某群里有人提了这样一个问题
就是在chrome调试器明明打印出来了xhr,能看到response的值,而在代码里面直接打印response却看不见这个值
结论
直接上结论,console.log打印的是xhr的引用,也就是说,open在打印的时候,xhr其实是对的,xhr.response也确实是空字符串的。但是后来请求完毕过后,xhr.response这个值被修改了,接着才是点开chrome dev-tool里面的XMLHttpRequest,所以这时候才能看到response,产生了一种错觉。
ajax-hook.js
hookAjax({
onreadystatechange:function(xhr){
console.log("onreadystatechange called: %O",xhr)
//return true
},
onload:function(xhr){
console.log("onload called: %O",xhr)
xhr.responseText="hook"+xhr.responseText;
//return true;
},
open:function(arg,xhr){
console.log("open called: method:%s,url:%s,async:%s",arg[0],arg[1],arg[2],xhr)
arg[1]+="?hook_tag=1";
//统一添加请求头
},
send:function(arg,xhr){
console.log("send called: %O",arg[0])
xhr.setRequestHeader("_custom_header_","ajaxhook")
},
setRequestHeader:function(arg,xhr){
console.log("setRequestHeader called!",arg)
}
})
对于原生的XMLHttpRequest对象的一些方法,比如open,send都是支持“重载”的(暂时用这个词)
也就是说它能够控制ajax的报文
原理其实重新了XMLHttpRequest对象
// 用._ahrealxhr保存原始的XMLHttpRequest对象
window._ahrealxhr = window._ahrealxhr || XMLHttpRequest
// 重写XMLHttpRequest对象
XMLHttpRequest = function () {
// 每次new XMLHttpRequest(假设创建对象为A)的时候
// 都偷偷地new一个原始的XMLHttpRequest,挂在A.xhr这个变量里面
this.xhr = new window._ahrealxhr;
// 遍历XMLHttpRequest的属性,都复制一份到对象A的属性上
for (var attr in this.xhr) {
var type = "";
try {
type = typeof this.xhr[attr]
} catch (e) {}
if (type === "function") {
// "成员方法"(暂时这么叫)
this[attr] = hookfun(attr);
} else {
// "成员变量"(也是暂时这么叫)
Object.defineProperty(this, attr, {
get: getFactory(attr),
set: setFactory(attr)
})
}
}
}
一、 接下来,看看 "成员方法" 是怎么实现"重载"
function hookfun(fun) {
return function () {
// [].slice.all(arguments)就是把arguments对象转化成array
var args = [].slice.call(arguments)
// 比方说,open函数,假如原本open的参数是arguments
// 那么重载后open的参数是[arguments, this.xhr]
if (funs[fun] && funs[fun].call(this, args, this.xhr)) {
return;
}
this.xhr[fun].apply(this.xhr, args);
}
}
如果不理解[].slice.call(arguments),那就试试下面的代码
function A() {
console.log("%O", arguments)
for(let i in arguments) {
console.log(i)
}
let args = [].slice.call(arguments);
console.log("%O", args)
for(let i in args) {
console.log(i)
}
}
从对比输出结果(我这里没有列出来),可以看到数组对象比arguments多出很多方法
二、 接下来,看看 "成员变量" 是怎么实现"重载"
Object.defineProperty(this, attr, {
get: getFactory(attr),
set: setFactory(attr)
})
function getFactory(attr) {
return function () {
// 这个this.xhr[attr]的其中一个原因是为了防止死循环暴栈而亡
// 这个this[attr + "_"] 要配合下面set方法才能明白
return this[attr + "_"] || this.xhr[attr]
}
}
function setFactory(attr) {
return function (f) {
var xhr = this.xhr;
var that = this;
if (attr.indexOf("on") != 0) {
this[attr + "_"] = f;
return;
}
if (funs[attr]) {
xhr[attr] = function () {
funs[attr](that) || f.apply(xhr, arguments);
}
} else {
xhr[attr] = f;
}
}
}
1.上面说暴栈,如果不理解,可以试试下面代码
var obj2 = {}
Object.defineProperty(obj2, "val", {
get : function(){ return this["val"] },
set : function(newValue){ this["val"] = newValue}
})
var b = obj2.val
//上面执行 = 的时候,会调用get,但是 get 函数里面,会去索引"val",如此循环下去
2.创建XMLHttpRequest的时候,它的大量回调函数,比方说onload、onprogress、onreadystatechange、ontimeout还是null值,在上层被赋值成函数,就会走到xhr[attr] = function那个地方的代码,首先执行我们的重载代码funsattr,再执行上层f.apply(xhr, arguments)
3.下滑线的主要目的是为了解决类似于responseText不可写,所以当修改responseText值的时候,实际修改的是responseText_
未经授权,禁止转载
by chainhelen