/* Ajax-hook是一种比较实用的技术,它可以自定义XMLHttpRequest中的方法和属性, 覆盖全局的XMLHttpRequest。当网站在使用Ajax请求动态渲染时,开发者可以对其中的 请求参数和响应数据进行任意修改和展示,有些类似前面讲过的本地覆盖,区别是这里 会去调用原本的XMLHttpRequest。 */ //2.4.1 Ajax-hook源码分析 !function (ob){ ob.hookAjax=function(funs){ window._ahrealxhr=window._ahrealxhr||XMLHttpRequest; XMLHttpRequest=function(){ /* 查看Ajax-hook的核心代码,发现它会将浏览器window对象传入,并先获取全局的真实XMLHttpRequest, 同时对XMLHttpRequest对象中的API接口进行遍历,其中的方法和属性会采取不同的策略进行处理。 如果遍历到的API接口类型是function,就会用hookfun方法处理, 属性会用Object.defineProperty方法进行定义,代码如下: */ this.xhr=new window._ahrealxhr; for (var attr in this.xhr) {//遍历XHR接口(可枚举) var type=""; try{ type=typeof this.xhr[attr]//获取接口类型 }catch(e){} if (type==="function"){ //方法 this[attr]=hookfun(attr); }else{ //属性 Object.defineProperty(this,attr,{ //get:在获取定义的属性时,会调用这里定义的函数。它不传入任何参数,但是会传入this对象。 //该函数的返回值会被用作属性的值。 get:getFactory(attr), //set:在定义的属性被修改时,会调用这里定义的函数。该方法会接受被定义的新值作为参数, //同时也会传入this对象。 set:setFactory(attr) }) } } }; /* Ajax-hook获取属性时调用getFactory函数,返回值使用了三元运算符。this.hasOwnProperty方法会返回一个布尔值, 用来表明this对象自身属性中是否具有指定的属性。 */ function getFactory(attr){ return function(){ //判断this对象自身属性中是否具有指定的属性 return this.hasOwnProperty(attr+"_")?this[attr+"_"]:this.xhr[attr]; //这里通常返回以on开头的属性对应的,用户自定义的方法调用 } } /* Ajax-hook运行周期内,修改属性会调用setFactory函数,它会接受被定义的新值作为参数,这里用f来指代。 如果属性以on开头,就会在开发者自定义的func中进行匹配并调用,开发者自定义的水星返回为true表示进行阻断, 自定义的函数执行完毕后不会再执行XHR原生函数,反之会继续执行XHR原先的回调。 */ function setFactory(attr){ return function(f){ var xhr=this.xhr; var that=this; if (attr.indexOf("on")!=0){ //Ajax-hook在遍历的属性后加上了下划线,在setFactory中对不以on开头的属性会进行下划线添加的新映射值 this[attr+"_"]=f; return; } if (funs[attr]){ xhr[attr]=function(){ funs[attr](that)||f.apply(xhr,arguments); } }else{ xhr[attr]=f; } } } //在对XMLHttpRequest中原生的方法进行处理时,会使用如下hookfun方法。 /* 它首先会把当前遍历来的方法中的参数赋值到args中,之后会获取开发者自定义的XMLHttpRequest方法, 如果能够获取到开发者自定义的方法,就会对当前方法进行覆盖,并将参数传入调用:如果没有获取到, 说明开发者没有重写这个方法,会继续调用原生方法。 处理XMLHttpRequest属性时,主要使用Object.defineProperty,这个方法的作用是直接在一个对象上 定义一个新的属性,或者修改一个已经存在的属性,代码如下: Object.defineProperty(obj,prop,descriptor) */ function hookfun(fun){ return function(){ var args=[].slice.call(arguments); if (funs[fun] && funs[fun].call(this,args,this.xhr)){ return; } return this.xhr[fun].apply(this.xhr,args); } } return window._ahrealxhr; } ob.unHookAjax=function(){ if (window._ahrealxhr) XMLHttpRequest=window._ahrealxhr; window._ahrealxhr=undefined; } }(window); //2.4.2 Ajax-hook拦截 /* 在使用Ajax技术的网页上,使用Ajax-hook进行代码调式会让开发者处理起来游刃有余。 XMLHttpRequest中的方法和属性都可以进行自由操作,所以能做的事也非常多,如可以 直接对网页的Ajax请求中的参数进行打印,也可以在发送Ajax请求的时候进行断点调式, 或直接将返回的响应数据进行展示,代码如下: */ hookAjax({ //检测到状态码变化,打印输出响应体 onreadystatechange:function(xhr){ if(xhr.status==200){ console.log("response:\n",xhr.response) //console.log("response") } return false;//不进行阻断 }, //在打开Ajax请求的时候进行操作 open:function(arg){ console.log("open called:\nmethod:%s,\nurl:%s,\nasync:%s\n",arg[0],arg[1],arg[2]) //console.log("open") }, //在发送Ajax请求的时候进行操作 send:function(arg){ debugger;//设置断点 console.log("send called:",arg) }, //在设置请求头部的时候进行操作 setRequestHeader:function(arg){ console.log("setRequestHearder:",arg) }, }) /* 百度图片(https://image.baidu.com/)的加载使用了Ajax技术,可用来做测试。将以上hook代码注入到网页中, 或直接输入Console控制中,查看Console面板的输出: Console面板会直接输出请求图片的Ajax接口,并且打印设置的headers头部, 还展示了响应体返回的JSON图片数据。 */