jQuery原始碼分析系列(33) : AJAX中的前置過濾器和請求分發器
jQuery1.5以後,AJAX模組提供了三個新的方法用於管理、擴充套件AJAX請求,分別是:
1.前置過濾器 jQuery. ajaxPrefilter
2.請求分發器 jQuery. ajaxTransport,
3.型別轉換器 ajaxConvert
原始碼結構:
jQuery.extend({ /** * 前置過濾器 * @type {[type]} */ ajaxPrefilter: addToPrefiltersOrTransports(prefilters), /** * 請求分發器 * @type {[type]}*/ ajaxTransport: addToPrefiltersOrTransports(transports), ........................... });
可見這2個方法是通過私有方法addToPrefiltersOrTransports通過curry手段構造的,分別是保持了prefilters與transports的引用
來個簡單的模擬這個結構
var prefilters = 2; var addToPrefiltersOrTransports = function(prefilters) {return function(b) { return prefilters + b; } } var ajaxPrefilter = addToPrefiltersOrTransports(prefilters) ajaxPrefilter(1) //3
可見ajaxPrefilter就維持了addToPrefiltersOrTransports返回函式的引用了,這種就是閉包的手法了,這也是JS的開發人員都需要掌握的
好處就是合併多個引數,當然因為維持引用代價就是一點點效能消耗
當然jQuery不是傳遞的簡單型別處理,還可以傳遞的一個引用型別的回撥函式,所以針對ajaxPrefilter方法放閉包構件就需要做一些處理了
填充prefilters處理器
var prefilters = {}; var addToPrefiltersOrTransports = function(structure) { return function(func) { structure['*'] = func; } } var ajaxPrefilter = addToPrefiltersOrTransports(prefilters) ajaxPrefilter(function(options){ return { send:function(){ }, callback:function(){ } } })
其實說白了就是把對應的方法制作能函式的形式填充到prefilters或者transports對應的處理包裝物件中
要用的時候直接執行,每個函式都保持著各自的引用
這種寫法的好處自然是靈活,易維護,減少程式碼量
還有我們經常的使用的,jQuery的程式碼很簡練,比如合併多個方法的建立等等
jQuery.each([ "tabIndex", "readOnly", "maxLength", "contentEditable" ], function() { jQuery.propFix[this.toLowerCase()] = this; });
所以此時的prefilters中的結構就是
prefilters = { '*': function() { return { send: function() { }, callback: function() { } } } }
迴歸重點,那麼引入ajaxPrefilter與ajaxTransport的作用是幹嘛?
前置過濾器和請求分發器在執行時,分別遍歷內部變數prefilters和transports,這兩個變數在jQuery載入完畢後立即初始化,從過閉包的方法填充這個2個物件
ajaxPrefilter與ajaxTransport都是通過inspectPrefiltersOrTransports構建器
prefilters中的前置過濾器在請求傳送之前、設定請求引數的過程中被呼叫,呼叫prefilters的是函式inspectPrefiltersOrTransports;
巧妙的是,transports中的請求分發器在大部分引數設定完成後,也通過函式inspectPrefiltersOrTransports取到與請求型別匹配的請求分發器:
function inspectPrefiltersOrTransports(structure, options, originalOptions, jqXHR) { var inspected = {}, seekingTransport = (structure === transports); function inspect(dataType) { var selected; inspected[dataType] = true; jQuery.each(structure[dataType] || [], function(_, prefilterOrFactory) { var dataTypeOrTransport = prefilterOrFactory(options, originalOptions, jqXHR); if (typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[dataTypeOrTransport]) { options.dataTypes.unshift(dataTypeOrTransport); inspect(dataTypeOrTransport); return false; } else if (seekingTransport) { return !(selected = dataTypeOrTransport); } }); return selected; } return inspect(options.dataTypes[0]) || !inspected["*"] && inspect("*"); }
遍歷structure[dataType]陣列,並執行回撥
prefilterOrFactory為函式陣列元素
執行該函式如果返回的結果dataTypeOrTransport是字串且時prefilters且沒有被inspected過
就給options.dataTypes陣列頭部新增該字串
繼續遞迴dataTypeOrTransport(當我們使用json/jsonp的時候會返回“script”,於是會執行“script”相關的回撥)
如果是transport就返回dataTypeOrTransport的假結果
前置過濾器 prefilters
簡單的說就是一種hack的做法,只是說比起事件的那種hack寫的手法實現更為高明
我們可以看看針對prefilters的方法其實就是dataType為 script,json,jsonp的處理
當我們動態載入指令碼檔案比如
$.ajax({ type : "GET", url : "test.js", dataType : "script" });
所以在inspectPrefiltersOrTransports方法中prefilters[script]能找到對應的處理方法,所以就會執行
例如script的hack,要強制加上處理快取的特殊情況和crossDomain
因為設定script的前置過濾器,script並不一定意思著跨域
跨域未被禁用,強制型別為GET,不觸發全域性時間
jQuery.ajaxPrefilter("script", function(s) { if (s.cache === undefined) { s.cache = false; } if (s.crossDomain) { s.type = "GET"; } });
所以prefilters就是在特定的環境針對特定的情況做一些必要的相容的處理
請求分發器 transports
請求分發器顧名思義傳送請求,那麼底層的ajax傳送請求是通過send方法
xhr.send();
但是jQuery對send方法做了拆分,把對應的處理放到了transports中了
那麼transports物件也是類似前置處理器通過jQuery.ajaxTransport構建
例如script,send,abort方法
返回出transports方法
transport = inspectPrefiltersOrTransports(transports, s, options, jqXHR);