✨长文 阅读约需十分钟jquery
✨跟着走一遍须要一小时以上git
✨约100行代码github
前段时间打算写一个给手机端用的假冒控制台 能够用来看console
的输出 这一块功能目前已经完成了ajax
可是后来知道有一个腾讯团队的项目vConsole
参考了一下发现他的功能丰富了不少 能够看Network
面板等设计模式
虽然是阉割过的面板 不过仍是能够看到一些网络相关的重要信息数组
因此本身也想着加上这个Network
面板功能浏览器
想实现这个功能呢 想到过几种方案 一种是封装一个ajax 可是这种不太现实 要让别人都用本身封装的ajax 对于项目中途须要引入的状况十分不友好 须要改动大量代码bash
因此这种方案是不可取的网络
以后选择的方案 就像这里就像实现console
面板同样 拦截了console下面的方法app
而拦截console
对象下的方法 实际上就是重写了他的方法
好比console.log
实际上就是log
方法进行了重写 在真正执行log
前 先作一些本身想作的事情
这一块一开始想到的实际上是XMLHrrpRequest
对象有全局的勾子函数 查阅了文档彷佛没有发现相关的内容
后来搜索了一些别的实现方式 大多数就是辣鸡站不知道从哪里爬来的内容 也没什么有价值的东西
后来在github上找到了一个仓库(Ajax-hook)
这个库实现的是能够拦截全局的Ajax请求 能够同同名方法或者属性做为勾子 达到拦截的效果
部份内容参考了他的实现思想
实际上实现方式和自定义的console
很像 底层运行依然靠原生的xhr对象 可是在上层作了代理 重写了XMLHttpRequest
对象
大致实现思路能够列成下面这几步
XMLHttpRequest
对象置为新的对象重写方法 作的主要就是在执行原生的方法前 提供一个钩子 来执行自定义的内容
重写属性 主要是一些事件触发的属性 好比onreadystatechange
同时还有别的坑点 xhr对象上不少属性是只读的(这一点也是经过Ajax-hook了解到的)
若是在钩子中对只读属性进行修改了 那在往下走也是没有用的 由于压根没有修改为功
不过既然大体思路有了 那就尝试着实现一下吧
实现以前 先想一下怎么使用吧 这样实现过程也会有一个大体的方向
先起个名字 就叫他Any-XHR
了
使用的时候经过new AnyXHR()
来建立实例
能够在构造函数中来进行钩子的添加 钩子有两种 一种是before
(执行前调用)的钩子 一种是after
(执行后调用)的钩子
就像这样 构造函数接受两个参数 都是对象 第一个是调用前执行的钩子 另外一种是调用后
对象里面的key
对应了原生xhr的方法和属性 保持一致便可
let anyxhr = new AnyXHR({
send: function(...args) {
console.log('before send');
}
}, {
send: function(...args) {
console.log('after send');
}
});
复制代码
有了目标 如今就来实现一下吧 按照所想的 咱们要用new
关键字来实例化一个对象 就要用构造函数或者class
关键字建立一个类
这里采用class关键字来声明
class AnyXHR {
constructor(hooks = {}, execedHooks = {}) {
this.hooks = hooks;
this.execedHooks = execedHooks;
}
init() {
// 初始化操做...
}
overwrite(proxyXHR) {
// 处理xhr对象下属性和方法的重写
}
}
复制代码
这里目前只关注构造函数
构造函数
接受的两个对象 就是表示执行前的钩子和执行后的钩子 对应的挂到实例的hooks
和execedHooks
属性上
按照刚才说的步骤 要保留原生的xhr对象 把这个步骤丢到构造函数中
而后作一些初始化操做 初始化的时候要对xhr对象重写
constructor(hooks = {}, execedHooks = {}) {
+ this.XHR = window.XMLHttpRequest;
this.hooks = hooks;
this.execedHooks = execedHooks;
+ this.init();
}
复制代码
init里 要作的就是对XMLHttpRequest
进行重写 一个xhr实例的每一个方法和属性进行重写
这样在new XMLHttpRequest()
的时候 new
出来的就是咱们本身重写的xhr实例了
init() {
window.XMLHttpRequest = function() {
}
}
复制代码
像这样 把XMLHttpRequest
赋值为一个新的函数 这样XMLHttpRequest
就不是原来的那个了 用户全局访问到的就是咱们本身写的这个新的函数
接着就是去实现重写像open
、onload
、send
这些原生xhr实例的方法和属性了
但是怎么重写呢 如今是个空函数 怎么让用户在new XMLHttpRequest
后 可使用send
、open
方法呢
其实以前有提到了 重写方法 作的主要就是在执行原生的方法前 提供一个钩子 来执行自定义的内容
既然要执行原生的方法 那咱们仍是须要用到原生的xhr对象
就拿open
来讲
在用户new XMLHttpRequest
的同时 须要再new
一个咱们保留下来的 原生的XMLHttpRequest
对象
而后在本身重写的XMLHttpRequest
实例上 挂一个send
方法 他作的 就是先执行自定义的内容 完了以后 去执行咱们new
出来的 保留下来的XMLHttpRequest
对象的实例下的open方法 同时把参数传递过去便可
听起来有点绕 能够画画图再细细品味一下
init() {
+ let _this = this;
window.XMLHttpRequest = function() {
+ this._xhr = new _this.XHR(); // 在实例上挂一个保留的原生的xhr实例
+ this.overwrite(this);
}
}
复制代码
这样在用户new XMLHttpRequest
的时候 内部就建立一个保留下来的 原生的XMLHttpRequest
实例
好比当用户调用send
方法的时候 咱们先执行本身想要作的事情 而后再调用this._xhr.send
执行原生xhr实例的send
方法就能够了 是否是很简单
完了以后咱们进入overwrite
方法 这个方法作的事情就是上一句所说的 对每一个方法和属性进行重写 其实就是在执行以前和执行以后动点手脚而已
调用_this.overwrite
的时候咱们须要把this传递过去 这里的this 指向的是new XMLHttpRequest
后的实例
属性和方法都是要挂到实例上的 供用户调用 因此把this传递过去 方便操做
这里在用新函数覆盖
window.XMLHttpRequest
的时候 不可使用箭头函数 箭头函数不能看成构造函数来使用 因此这里要用this保留的作法
要重写方法和属性 要重写的就是send
、open
、responseText
、onload
、onreadystatechange
等这些
那要一个一个实现吗... 确定是不现实的
要重写的方法和属性 咱们能够经过便利一个原生的xhr实例来获取
原生的xhr实例 已经保留在了覆盖后的XMLHttpRequest
对象的实例的_xhr
属性上
因此经过遍历_xhr
这个原生的xhr实例 就能够拿到须要全部要重写的方法和属性 的key
和value
overwrite(proxyXHR) {
+ for (let key in proxyXHR._xhr) {
+ }
}
复制代码
这里的proxyXHR
参数指向的是一个修改后的XMLHttpRequest
实例
方法和属性要挂到实例上
现对proxyXHR
下的_xhr
属性进行遍历 能够拿到他下面的全部可遍历的属性和方法
而后区分方法和属性 作不一样的处理便可
overwrite(proxyXHR) {
for (let key in proxyXHR._xhr) {
+ if (typeof proxyXHR._xhr[key] === 'function') {
+ this.overwriteMethod(key, proxyXHR);
+ continue;
+ }
+ this.overwriteAttributes(key, proxyXHR);
}
}
复制代码
经过typeof
来判断当前遍历到的是属性仍是方法 若是是方法 则调用overwriteMethod
对这个方法进行改造重写
若是是属性 则经过overwriteAttributes
对这个属性进行改造重写
同时把遍历到的属性名和方法名 以及修改后的XMLHttpRequest
实例传递过去
那接下来就来实现一下这里两个方法
在类中添加这个方法
class AnyXHR {
+ overwriteMethod(key, proxyXHR) {
// 对方法进行重写
+ }
}
复制代码
这个方法作的 就是对原生的xhr实例下的方法进行重写处理
其实严格来讲 把这个操做称做重写是不严格的
就拿send
方法来讲 并不会对原生的xhr实例下的send
方法进行修改 而写新写一个方法 挂到本身实现的xhr实例上 来代替原生的xhr实例来执行 最终send
的过程 仍是调用原生的send
方法 只是在调用前和调用后 多作了两件别的事情
因此这里就是对每一个方法 作一个包装
overwriteMethod(key, proxyXHR) {
+ let hooks = this.hooks;
+ let execedHooks = this.execedHooks;
+ proxyXHR[key] = (...args) => {
+ }
}
复制代码
首先保留了hooks
和execedHooks
等下会频繁用到
而后咱们往新的xhr实例上挂上同名的方法 好比原生的xhr有个send
遍历到send
的时候 这里进来的key就是send
因此就会往新的xhr实例上挂上一个send
来替代原生的send
方法
当方法被调用的时候 会拿到一堆参数 参数是js引擎(或者说浏览器)丢过来的 这里用剩余参数把他们都接住 组成一个数组 调用钩子的时候 或者原生方法的时候 能够再传递过去
那这里面具体作些什么操做呢
其实就三步
overwriteMethod(key, proxyXHR) {
let hooks = this.hooks;
let execedHooks = this.execedHooks;
proxyXHR[key] = (...args) => {
+ // 若是当前方法有对应的钩子 则执行钩子
+ if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) {
+ return;
+ }
+ // 执行原生xhr实例中对应的方法
+ const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);
+ // 看看还有没有原生xhr实例对应的方法执行后须要执行的钩子 若是有则执行
+ execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res);
+ return res;
}
}
复制代码
首先第一步 hooks[key]
就是判断当前的方法 有没有对应的钩子函数
hooks对象里面保存的就是全部的钩子函数 或者说须要拦截的方法 是在咱们执行new AnyXHR(..)
的时候传进来的
若是有 则执行 并把参数传递给钩子 若是这个钩子函数返回了false 则停止 不往下走了 这样能够起到拦截的效果
不然就往下走 执行原生xhr实例中对应的方法
原生的xhr实例放在了_xhr
这个属性里 因此经过proxyXHR._xhr[key]
就能够访问到 同时把参数用apply
拍散 传递过去就行了 同时接住返回值
完了以后走第三步
看看有没有执行完原生xhr方法后还要执行的钩子 若是有则执行 而后把返回值传递过去
以后返回原生xhr实例对应的方法执行后反过来的返回值就行了
到这了方法的代理、拦截就完成了 能够去尝试一下了
记得注释一下
this.overwriteAttributes(key, proxyXHR);
这一行
虽然到如今代码很少 不过一会儿没绕过来 仍是很累的 能够先倒杯水休息一下
到目前为止的完整代码以下
class AnyXHR {
constructor(hooks = {}, execedHooks = {}) {
this.XHR = window.XMLHttpRequest;
this.hooks = hooks;
this.execedHooks = execedHooks;
this.init();
}
init() {
let _this = this;
window.XMLHttpRequest = function() {
this._xhr = new _this.XHR(); // 在实例上挂一个保留的原生的xhr实例
_this.overwrite(this);
}
}
overwrite(proxyXHR) {
for (let key in proxyXHR._xhr) {
if (typeof proxyXHR._xhr[key] === 'function') {
this.overwriteMethod(key, proxyXHR);
continue;
}
// this.overwriteAttributes(key, proxyXHR);
}
}
overwriteMethod(key, proxyXHR) {
let hooks = this.hooks;
let execedHooks = this.execedHooks;
proxyXHR[key] = (...args) => {
// 若是当前方法有对应的钩子 则执行钩子
if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) {
return;
}
// 执行原生xhr实例中对应的方法
const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);
// 看看还有没有原生xhr实例对应的方法执行后须要执行的钩子 若是有则执行
execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res);
return res;
}
}
}
复制代码
尝试一下第一次调试
new AnyXHR({
open: function () {
console.log(this);
}
});
var xhr = new XMLHttpRequest();
xhr.open('GET', '/abc?a=1&b=2', true);
xhr.send();
复制代码
能够打开控制台 看看是否有输出 同时能够观察一下对象 和_xhr
属性下内容坐下对比看看
这个方法实现稍微麻烦些 内容也会绕一些
可能有一个想法 为何要监听 或者说代理 再或者说给属性
提供钩子呢 有什么用吗
像responseText
这种属性 其实不是目标
目标是给像onreadystatechange
、onload
这样的属性包装一层
这些属性有点相似事件 或者说就是事件
使用的时候须要手动给他们赋值 在对应的时刻会自动调用 能够说是原生xhr
提供的钩子
像send
、open
能够在请求发出去的时候拦截
而onreadystatechange
、onload
能够用来拦截服务的响应的请求
因此有必要对这些属性进行包装
知道了为何须要对属性作个包装 问题就到了怎么去包装属性了
那onload
作例子
xhr.onload = function() {
...
};
复制代码
在用户这样赋值的时候 咱们应该作出一些响应
捕获到这个赋值的过程
而后去看看 hooks
这个数组中有没有onload
的钩子呀
若是有的话 那就在执行原生的xhr实例的onload以前 执行一下钩子就ok了
那问题来时 普通的属性怎么办 好比responseType
那这些属性就不处理了 直接挂到新的xhr实例上去
又有问题了 怎么区分普通的属性 和事件同样的属性呢
其实观察一下就知道 on
打头的属性 就是事件同样的属性了
因此总结一下
逻辑理清楚了 是否是发现很简单
好 那就动手吧
??等等 怎么监听用户给onload
这样的属性赋值啊???
能够停一下 仔细想一想
这一块就能够用到ES5中提供的 getter
和setter
方法
这个知识点确定是很熟悉的 不熟悉能够去翻一下MDN
经过这两个方法 就能够监听到用户对一个属性赋值和取值的操做 同时能够作一些额外的事情
那怎么给要插入的属性设置get/set方法呢
ES5提供了Object.defineProperty
方法
能够给一个对象定义属性 同时能够指定她的属性描述符 属性描述符中能够描述一个属性是否可写 是否能够枚举 还有他的set/get等
固然使用字面量的方式 为一个属性定义get/set方法也是能够的
扯了这么多 那就实现一下这个方法吧
overwriteAttributes(key, proxyXHR) {
Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR));
}
复制代码
这里就一行代码 就是用Object.defineProperty
给本身实现的xhr实例上挂个属性 属性名就是传递过来的key 而后用setProperyDescriptor
方法生成属性描述符 同时把key和实例传递过去
描述符里面会生成get/set方法 也就是这个属性赋值和取值时候的操做
一样的 往类里面加入这个方法
setProperyDescriptor(key, proxyXHR) {
}
复制代码
能够拿到要添加的属性名(key)和本身实现的xhr对象实例
属性都要挂到这个实例上
setProperyDescriptor(key, proxyXHR) {
+ let obj = Object.create(null);
+ let _this = this;
}
复制代码
属性描述符其实是个对象
这里用Object.create(null)
来生成一个绝对干净的对象 防止有一些乱起八糟的属性出现 阴差阳错变成描述
而后保留一下this
以后就实现一下set
setProperyDescriptor(key, proxyXHR) {
let obj = Object.create(null);
let _this = this;
+ obj.set = function(val) {
+ }
}
复制代码
set方法会在属性被赋值的时候(好比obj.a = 1
)被调用 他会拿到一个参数 就是赋值的值(等号右边的值)
而后在里面 就能够作以前罗列的步骤了
setProperyDescriptor(key, proxyXHR) {
let obj = Object.create(null);
let _this = this;
obj.set = function(val) {
+ // 看看属性是否是on打头 若是不是 直接挂上去
+ if (!key.startsWith('on')) {
+ proxyXHR['__' + key] = val;
+ return;
+ }
+ // 若是是 则看看有没有要执行的钩子
+ if (_this.hooks[key]) {
+ // 若是有 则包装一下 先执行钩子 再执行本体
+ this._xhr[key] = function(...args) {
+ (_this.hooks[key].call(proxyXHR), val.apply(proxyXHR, args));
+ }
+ return;
+ }
+ // 若是没有 责直接赋值挂上去
+ this._xhr[key] = val;
}
+ obj.get = function() {
+ return proxyXHR['__' + key] || this._xhr[key];
+ }
+ return obj;
}
复制代码
第一步的时候 判断了是否是on打头 不是则原模原样挂到实例上
而后去看看当前的这个属性 在钩子的列表中有没有 有的话 就把要赋的值(val)和钩子一块儿打包 变成一个函数
函数中先执行钩子 再执行赋的值 只要用的人不瞎鸡儿整 那这里的值基本是函数没跑 因此不判断直接调用 同时参数传递过去
若是没有钩子呢 则直接赋值就行了
这样set方法就ok了
get方法就简单一些了 就是拿值而已
可能有一个地方不太能理解 就是proxyXHR['__' + key] = val;
get方法中也有
为何这里有个__
前缀 其实这样
考虑这么一个场景 在拦截请求返回的时候 能够拿到responseText
属性
这个属性就是服务端返回的值
可能在这个时候会须要根据responseType
统一的处理数据类型
若是是JSON 那就this.responseText = JSON.parse(this.response)
而后就满心欢喜的去成功的回调函数中拿responseText
结果在对他进行属性或者方法访问的时候报错了 打印一下发现他仍是字符串 并无转成功
其实就是由于responseText
是只读的 在这个属性的标签中 writable
是false
因此能够用到一个代理属性来解决
好比this.responseText = JSON.parse(this.responseText)
的时候
首先根据get方法 去拿responseText
这个时候尚未__responseText
属性 因此回去原生的xhr实例拿 拿到的就是服务端回来的值
而后通过解析后 又被赋值
复制的时候 在本身实现的xhr实例中 就会多一个__responseText
属性 他的值是通过处理后的
那以后再经过responseText
取值 经过get方法拿到的就是__responseText
的值
这样就经过一层属性的代理 解决了原生xhr实例属性只读的问题
这样大部分逻辑都完成了 记得把前面this.overwriteAttributes(key, proxyXHR);
的注释去掉
到这里被绕晕是有可能的 不用太在乎 仔细理一下画画图就行了 倒杯水冷静一下
这是到目前为止的完整代码 能够去尝试一下
class AnyXHR {
constructor(hooks = {}, execedHooks = {}) {
this.XHR = window.XMLHttpRequest;
this.hooks = hooks;
this.execedHooks = execedHooks;
this.init();
}
init() {
let _this = this;
window.XMLHttpRequest = function () {
this._xhr = new _this.XHR(); // 在实例上挂一个保留的原生的xhr实例
_this.overwrite(this);
}
}
overwrite(proxyXHR) {
for (let key in proxyXHR._xhr) {
if (typeof proxyXHR._xhr[key] === 'function') {
this.overwriteMethod(key, proxyXHR);
continue;
}
this.overwriteAttributes(key, proxyXHR);
}
}
overwriteMethod(key, proxyXHR) {
let hooks = this.hooks;
let execedHooks = this.execedHooks;
proxyXHR[key] = (...args) => {
// 若是当前方法有对应的钩子 则执行钩子
if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) {
return;
}
// 执行原生xhr实例中对应的方法
const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);
// 看看还有没有原生xhr实例对应的方法执行后须要执行的钩子 若是有则执行
execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res);
return res;
}
}
setProperyDescriptor(key, proxyXHR) {
let obj = Object.create(null);
let _this = this;
obj.set = function (val) {
// 看看属性是否是on打头 若是不是 直接挂上去
if (!key.startsWith('on')) {
proxyXHR['__' + key] = val;
return;
}
// 若是是 则看看有没有要执行的钩子
if (_this.hooks[key]) {
// 若是有 则包装一下 先执行钩子 再执行本体
this._xhr[key] = function (...args) {
(_this.hooks[key].call(proxyXHR), val.apply(proxyXHR, args));
}
return;
}
// 若是没有 责直接赋值挂上去
this._xhr[key] = val;
}
obj.get = function () {
return proxyXHR['__' + key] || this._xhr[key];
}
return obj;
}
overwriteAttributes(key, proxyXHR) {
Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR));
}
}
复制代码
调用
new AnyXHR({
open: function () {
console.log('open');
},
onload: function () {
console.log('onload');
},
onreadystatechange: function() {
console.log('onreadystatechange');
}
});
$.get('/aaa', {
b: 2,
c: 3
}).done(function (data) {
console.log(1);
});
var xhr = new XMLHttpRequest();
xhr.open('GET', '/abc?a=1&b=2', true);
xhr.send();
xhr.onreadystatechange = function() {
console.log(1);
}
复制代码
引入一下jquery 能够尝试着拦截
观察一下控制台 就能看到结果了
接下来还有一些内容须要完成 让整个类更完善 不过剩下的都是很简单的内容了
由于是全局拦截 并且全局下只有一个XMLHttpRequest
对象 因此这里应该设计成单例
单例是设计模式中的一种 其实没那么玄乎 就是全局下只有一个实例
也就是说得动点手脚 怎么new AnyXHR
拿到的都是同一个实例
修改一下构造函数
constructor(hooks = {}, execedHooks = {}) {
// 单例
+ if (AnyXHR.instance) {
+ return AnyXHR.instance;
+ }
this.XHR = window.XMLHttpRequest;
this.hooks = hooks;
this.execedHooks = execedHooks;
this.init();
+ AnyXHR.instance = this;
}
复制代码
进入构造函数 先判断AnyXHR
上有没有挂实例 若是有 则直接返回实例 若是没有 则进行建立
而后建立流程走完了以后 把AnyXHR
上挂个实例就行了 这样无论怎么new
拿到的都是都是同一个实例
同时再加一个方法 能够方便拿到实例
getInstance() {
return AnyXHR.instance;
}
复制代码
全部的钩子都维护在两个对象内 每一次方法的执行 都会去读这两个对象 因此只要让对象发生改变 就能动态的加钩子
因此加入一个add方法
add(key, value, execed = false) {
if (execed) {
this.execedHooks[key] = value;
} else {
this.hooks[key] = value;
}
return this;
}
复制代码
其中key
value
两个参数对应的就是属性名 或者方法名和值
execed
表明是否是原生的方法执行后再执行 这个参数用来区分添加到哪一个对象中
一样的道理 去掉钩子和清空钩子就很简单了
rmHook(name, isExeced = false) {
let target = (isExeced ? this.execedHooks : this.hooks);
delete target[name];
}
复制代码
clearHook() {
this.hooks = {};
this.execedHooks = {};
}
复制代码
这一步其实很简单 把咱们本身实现的 重写的XMLHttpRequest
变成原来的就行了
原来的咱们保留在了this.XHR
上
unset() {
window.XMLHttpRequest = this.XHR;
}
复制代码
既然是单例 从新开启监听 那只要把单例清了 从新new就行了
reset() {
AnyXHR.instance = null;
AnyXHR.instance = new AnyXHR(this.hooks, this.execedHooks);
}
复制代码
class AnyXHR {
/**
* 构造函数
* @param {*} hooks
* @param {*} execedHooks
*/
constructor(hooks = {}, execedHooks = {}) {
// 单例
if (AnyXHR.instance) {
return AnyXHR.instance;
}
this.XHR = window.XMLHttpRequest;
this.hooks = hooks;
this.execedHooks = execedHooks;
this.init();
AnyXHR.instance = this;
}
/**
* 初始化 重写xhr对象
*/
init() {
let _this = this;
window.XMLHttpRequest = function() {
this._xhr = new _this.XHR();
_this.overwrite(this);
}
}
/**
* 添加勾子
* @param {*} key
* @param {*} value
*/
add(key, value, execed = false) {
if (execed) {
this.execedHooks[key] = value;
} else {
this.hooks[key] = value;
}
return this;
}
/**
* 处理重写
* @param {*} xhr
*/
overwrite(proxyXHR) {
for (let key in proxyXHR._xhr) {
if (typeof proxyXHR._xhr[key] === 'function') {
this.overwriteMethod(key, proxyXHR);
continue;
}
this.overwriteAttributes(key, proxyXHR);
}
}
/**
* 重写方法
* @param {*} key
*/
overwriteMethod(key, proxyXHR) {
let hooks = this.hooks;
let execedHooks = this.execedHooks;
proxyXHR[key] = (...args) => {
// 拦截
if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) {
return;
}
// 执行方法本体
const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);
// 方法本体执行后的钩子
execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res);
return res;
};
}
/**
* 重写属性
* @param {*} key
*/
overwriteAttributes(key, proxyXHR) {
Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR));
}
/**
* 设置属性的属性描述
* @param {*} key
*/
setProperyDescriptor(key, proxyXHR) {
let obj = Object.create(null);
let _this = this;
obj.set = function(val) {
// 若是不是on打头的属性
if (!key.startsWith('on')) {
proxyXHR['__' + key] = val;
return;
}
if (_this.hooks[key]) {
this._xhr[key] = function(...args) {
(_this.hooks[key].call(proxyXHR), val.apply(proxyXHR, args));
}
return;
}
this._xhr[key] = val;
}
obj.get = function() {
return proxyXHR['__' + key] || this._xhr[key];
}
return obj;
}
/**
* 获取实例
*/
getInstance() {
return AnyXHR.instance;
}
/**
* 删除钩子
* @param {*} name
*/
rmHook(name, isExeced = false) {
let target = (isExeced ? this.execedHooks : this.hooks);
delete target[name];
}
/**
* 清空钩子
*/
clearHook() {
this.hooks = {};
this.execedHooks = {};
}
/**
* 取消监听
*/
unset() {
window.XMLHttpRequest = this.XHR;
}
/**
* 从新监听
*/
reset() {
AnyXHR.instance = null;
AnyXHR.instance = new AnyXHR(this.hooks, this.execedHooks);
}
}
复制代码
到此呢总体就完成了 因为缺少测试 指不定还有bug
另外有些缺陷 就是全部钩子得是同步的 若是是异步顺序会乱 这个问题以后再解决 若是感兴趣能够本身也尝试一下
另外这种拦截的方式 基本上适用任何对象 能够灵活的使用
只要是使用XMLHttpRequest
的ajax请求 均可以用他来拦截