摘要
長文 閱讀約需十分鐘
跟着走一遍需要一小時以上
約100行代碼
前段時間打算寫一個給手機端用的假冒控制台 可以用來看
console
的輸出 這一塊功能目前已經完成了
但是後來知道有一個騰訊團隊的項目
vConsole
參考了一下發現他的功能豐富了很多 可以看
Network
面闆等
雖然是閹割過的面闆 不過還是可以看到一些網絡相關的重要資訊
是以自己也想着加上這個
Network
面闆功能
想實作這個功能呢 想到過幾種方案 一種是封裝一個ajax 但是這種不太現實 要讓别人都用自己封裝的ajax 對于項目中途需要引入的情況十分不友好 需要改動大量代碼
是以這種方案是不可取的
之後選擇的方案 就像這裡就像實作
console
面闆一樣 攔截了console下面的方法
而攔截
console
對象下的方法 實際上就是重寫了他的方法
比如
console.log
實際上就是
log
方法進行了重寫 在真正執行
log
前 先做一些自己想做的事情
思路
這一塊一開始想到的其實是
XMLHrrpRequest
對象有全局的勾子函數 查閱了文檔似乎沒有發現相關的内容
後來搜尋了一些别的實作方式 大多數就是辣雞站不知道從哪裡爬來的内容 也沒什麼有價值的東西
後來在github上找到了一個倉庫(
Ajax-hook)
這個庫實作的是可以攔截全局的Ajax請求 可以同同名方法或者屬性作為勾子 達到攔截的效果
部分内容參考了他的實作思想
實際上實作方式和自定義的
console
很像 底層運作依然靠原生的xhr對象 但是在上層做了代理 重寫了
XMLHttpRequest
對象
大體實作思路可以列成下面這幾步
- 保留原生xhr對象
- 将
對象置為新的對象XMLHttpRequest
- 對xhr對象的方法進行重寫後放入到新的對象中
- 對屬性進行重寫 然後放入到新對象中
重寫方法 做的主要就是在執行原生的方法前 提供一個鈎子 來執行自定義的内容
重寫屬性 主要是一些事件觸發的屬性 比如
onreadystatechange
同時還有别的坑點 xhr對象上很多屬性是隻讀的(這一點也是通過
了解到的)
如果在鈎子中對隻讀屬性進行修改了 那在往下走也是沒有用的 因為壓根沒有修改成功
不過既然大緻思路有了 那就嘗試着實作一下吧
實作
實作之前 先想一下怎麼使用吧 這樣實作過程也會有一個大緻的方向
先起個名字 就叫他
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
關鍵字建立一個類
這裡随口提一下 ES6的提供的class并不是真正 類似Java或者C++中的類 可以了解成是一種文法糖 本質上還是原型繼承 或者說是基于原型代理的 了解的大佬這句話跳過就好
這裡采用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 方法
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保留的做法
overwrite 方法
要重寫方法和屬性 要重寫的就是
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
執行個體傳遞過去
那接下來就來實作一下這裡兩個方法
overwriteMethod
在類中添加這個方法
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引擎(或者說浏覽器)丢過來的 這裡用剩餘參數把他們都接住 組成一個數組 調用鈎子的時候 或者原生方法的時候 可以再傳遞過去
那這裡面具體做些什麼操作呢
其實就三步
- 如果目前方法有對應的鈎子 則執行鈎子
- 執行原生xhr執行個體中對應的方法
- 看看還有沒有原生xhr執行個體對應的方法執行後需要執行的鈎子 如果有則執行
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
屬性下内容坐下對比看看
overwriteAttributes 方法
這個方法實作稍微麻煩些 内容也會繞一些
可能有一個想法 為什麼要監聽 或者說代理 再或者說給
屬性
提供鈎子呢 有什麼用嗎
像
responseText
這種屬性 其實不是目标
目标是給像
onreadystatechange
onload
這樣的屬性包裝一層
這些屬性有點類似事件 或者說就是事件
使用的時候需要手動給他們指派 在對應的時刻會自動調用 可以說是原生
xhr
提供的鈎子
send
open
可以在請求發出去的時候攔截
而
onreadystatechange
onload
可以用來攔截服務的響應的請求
是以有必要對這些屬性進行包裝
知道了為什麼需要對屬性做個包裝 問題就到了怎麼去包裝屬性了
那
onload
做例子
xhr.onload = function() {
...
};
複制代碼
在使用者這樣指派的時候 我們應該做出一些響應
捕獲到這個指派的過程
然後去看看
hooks
這個數組中有沒有
onload
的鈎子呀
如果有的話 那就在執行原生的xhr執行個體的onload之前 執行一下鈎子就ok了
那問題來時 普通的屬性怎麼辦 比如
responseType
那這些屬性就不處理了 直接挂到新的xhr執行個體上去
又有問題了 怎麼區分普通的屬性 和事件一樣的屬性呢
其實觀察一下就知道
on
打頭的屬性 就是事件一樣的屬性了
是以總結一下
- 看看屬性是不是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 方法
同樣的 往類裡面加入這個方法
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請求 都可以用他來攔截
如果可以請給個星星 過段時間找實習就靠了 萬分感謝!
原文釋出時間:2018年06月29日
原文作者:NISAL
本文來源
掘金,如需轉載請緊急聯系作者