自上一篇《js 攔截全局 ajax 請求》發佈以後,不少人對實現原理很是感興趣,好,今天咱們講內涵!javascript
若是你還不知道ajax-hook,請先了解一下:
github : github.com/wendux/Ajax…
中文介紹:www.jianshu.com/p/9b634f1c9…java
咱們首先在github上拉取源碼。納尼,這麼屌炸天的功能,源碼算上註釋和換行總共才67行!🤔,下面咱們來一步步揭開其神祕面紗。git
Ajax-hook實現的總體思路是實現一個XMLHttpRequest的代理對象,而後覆蓋全局的XMLHttpRequest,這樣一但上層調用 new XMLHttpRequest這樣的代碼時,其實建立的是Ajax-hook的代理對象實例。具體原理圖以下:github
上圖中青色部分爲Ajax-hook實現的代理XMLHttpRequest,內部會調用真正的XMLHttpRequest。咱們看一下hookAjax的部分源碼:ajax
ob.hookAjax = function (funs) {
//保存真正的XMLHttpRequest對象
window._ahrealxhr = window._ahrealxhr || XMLHttpRequest
//1.覆蓋全局XMLHttpRequest,代理對象
XMLHttpRequest = function () {
//建立真正的XMLHttpRequest實例
this.xhr = new window._ahrealxhr;
for (var attr in this.xhr) {
var type = "";
try {
type = typeof this.xhr[attr]
} catch (e) {}
if (type === "function") {
//2.代理方法
this[attr] = hookfun(attr);
} else {
//3.代理屬性
Object.defineProperty(this, attr, {
get: getFactory(attr),
set: setFactory(attr)
})
}
}
}
......複製代碼
Ajax-hook 一開始先保存了真正的XMLHttpRequest對象到一個全局對象,而後在註釋1處,Ajax-hook覆蓋了全局的XMLHttpRequest對象,這就是代理對象的具體實現。在代理對象內部,首先建立真正的XMLHttpRequest實例,記爲xhr,而後遍歷xhr全部屬性和方法,在2處hookfun爲xhr的每個方法生成一個代理方法,在3處,經過defineProperty爲每個屬性生成一個代理屬性。下面咱們重點看一看代理方法和代理屬性的實現。json
代理方法經過hookfun函數生成,咱們看看hookfun的具體實現:數組
function hookfun(fun) {
return function () {
var args = [].slice.call(arguments)
//1.若是fun攔截函數存在,則先調用攔截函數
if (funs[fun] && funs[fun].call(this, args, this.xhr)) {
return;
}
//2.調用真正的xhr方法
this.xhr[fun].apply(this.xhr, args);
}
}複製代碼
爲了敘述清晰,咱們假設fun爲 send函數,其中funs爲用戶提供的攔截函數對象。代碼很簡單,首先會根據用戶提供的funs判斷用戶是否要攔截send, 若是提供了send的攔截方法,記爲send_hook, 則上層調用代理對象send方法時,則會先調用send_hook,同時將調用參數和當前的xhr對象傳遞給send_hook,若是send_hook返回了true, 則調用終止,直接返回,至關於調用被終止了,若是沒有返回或返回的是false,則會走到註釋2處,此處調用了xhr的send方法,至此ajax send被調用成功。 因此,咱們在send_hook中能夠拿到調用的參數並修改,由於參數是以數組形式傳遞,改變會被記錄,固然,咱們也能夠返回true直接終止調用。app
屬性如onload、onreadystatechange等,上層在調用ajax時一般要設置這些回調以處理請求到的數據,Ajax-hook也可以實如今請求返回時先拿到數據第一個進行處理,而後將處理過的數據傳遞給用戶提供的回調。要實現這個功能,直接的思路就是用戶設置回調時將用戶提供的回調保存起來,而後設置成代理回調,當數據返回時,代理回調會被調用,而後在代理回調中首先將返回的數據提供給攔截函數處理,而後再將處理後的數據傳遞給用戶真正的回調。那麼問題來了,如何捕獲用戶設置回調的動做?一段典型的用戶調用代碼以下:函數
var xh=new XMLHttpRequest;
xh.open("https://xxx")
xh.onload=function(data){ //1
//處理請求到的數據
}複製代碼
也就是說上面代碼1處的賦值時機代理對象怎麼捕獲?若是在賦值的時候有機會執行代碼就行了。咱們回過頭來看看上面原理圖,有沒有注意到proxy props後面的小括號裏的 es5,答案就在這裏! es5中對於屬性引入了setter、getter,詳細內容請參考:
Javascript getter: developer.mozilla.org/en-US/docs/…
Javascript setter: developer.mozilla.org/en-US/docs/…源碼分析
Ajax-hook經過getFactory和setFactory生成setter、getter方法。咱們來看看它們的實現:
function getFactory(attr) {
return function () {
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;
}
}
}複製代碼
代碼比較簡單,值得注意的是裏面的屬性加下劃線是什麼意思?請繼續往下看。
若是須要對返回的數據進行加工處理,好比返回的數據是json字符串,若是你想將它轉化爲對象再傳遞給上層,你會在onload回調中這麼寫:
xhr.responseText = JSON.parse(xhr.responseText)複製代碼
可是,這裏有坑,由於xhr的responseText屬性並非writeable的(詳情請移步 developer.mozilla.org/en-US/docs/… ),這也就意味着你沒法直接更改xhr.responseText的值,而Ajax-hook也代理了這些原始屬性,內部生成了一下原始屬性名+下滑線的代理屬性。
至此,Ajax-hook源碼分析完畢。下面咱們總結一下:
Ajax-hook使用代理的方式對原生XMLHttpRequest的方法及屬性進行代理,而後覆蓋全局XMLHttpRequest,實現攔截全部Ajax-hook的功能。從代碼角度來看,邏輯清晰,思惟巧妙,簡潔優雅,值得學習。
若是你喜歡,就去 github star一下吧,地址 github.com/wendux/Ajax… 。
本文章容許免費轉載,但請註明原做者及原文連接。