jQuery原始碼分析系列(37) : Ajax 總結
綜合前面的分析,我們總結如下3大塊:
- jQuery1.5以後,AJAX模組提供了三個新的方法用於管理、擴充套件AJAX請求
- 前置過濾器 jQuery. ajaxPrefilter
- 請求分發器 jQuery. ajaxTransport
- 型別轉換器 ajaxConvert
- 為了整體性與擴充套件性考慮,把整個結構通過Deferred實現非同步鏈式模型,Promise物件可以輕易的繫結成功、失敗、進行中三種狀態的回撥函式,然後通過在狀態碼在來回調不同的函式就行了
- 出於同源策略考慮,存在跨域問題,所以ajax內部的處理總的來分2大塊
- 基於XMLHttpRequest的ajax請求
- 基於script的jsonp跨域請求
引入Deferred統一回調體系
jQuery的鏈式方法是大是通過返回this的引用,但是ajax的鏈式不是那麼簡單的,因為ajax可以非同步操作,所以返回的是一個非同步模型物件Promise
當然如果只是deferred = jQuery.Deferred() 返回這個物件也是沒意義的,因為無法關聯到實際的資料
所以jquery內部構建了一個增強版的jqXHR物件,除了混入Promise模型,還增強了一些方法與介面
jqXHR 擴充基本的方法與介面
jqXHR = { // 準備狀態 readyState: 0,// Builds headers hashtable if needed // 如果需要,建立一個響應頭引數的表 getResponseHeader: function(key) { var match; // 如果狀態為2,狀態2表示ajax完成 if (state === 2) { // 如果沒有相應頭 if (!responseHeaders) { // 相應頭設空 responseHeaders = {};// 組裝相應頭 while ((match = rheaders.exec(responseHeadersString))) { responseHeaders[match[1].toLowerCase()] = match[2]; } } // 響應頭對應的key的值 match = responseHeaders[key.toLowerCase()]; } return match == null ? null : match; }, // Raw string // 返回響應頭字串 getAllResponseHeaders: function() { // 看看是否接收到了,接收到直接返回,否則為null return state === 2 ? responseHeadersString : null; }, // Caches the header // 設定請求頭 setRequestHeader: function(name, value) { var lname = name.toLowerCase(); if (!state) { // 如果requestHeadersNames[ lname ]不為空, // 則requestHeadersNames[ lname ]不變,name設定為該值 // 否則,requestHeadersNames[ lname ]不空, // 則requestHeadersNames[ lname ]設定為name, // 該對映關係用於避免使用者大小寫書寫錯誤之類的問題,容錯處理 name = requestHeadersNames[lname] = requestHeadersNames[lname] || name; //現在的name是對的,或者是第一次設定這個name,不需要容錯 //設定請求頭對應值 requestHeaders[name] = value; } return this; }, // Overrides response content-type header // 重寫相應頭content-type overrideMimeType: function(type) { if (!state) { s.mimeType = type; } return this; }, // Status-dependent callbacks // 對應狀態的回撥函式集 statusCode: function(map) { var code; if (map) { //如果狀態小於2,表示舊的回撥可能還沒有用到 if (state < 2) { for (code in map) { // Lazy-add the new callback in a way that preserves old ones // 用類似連結串列的方式新增,以保證舊的回撥依然存在 statusCode[code] = [statusCode[code], map[code]]; } } else { // Execute the appropriate callbacks // 無論Deferred成功還是失敗都執行當前狀態回撥 jqXHR.always(map[jqXHR.status]); } } return this }, // Cancel the request abort: function(statusText) { var finalText = statusText || strAbort; if (transport) { transport.abort(finalText); } done(0, finalText); return this; } };
看看我們ajax的寫法
$.ajax({ url: "php.html", context: document.body, complete: function() { console.log(this) } }).done(function() { console.log(this) });
鏈式了一個done方法,done是Promise模型中的成功回撥,因為ajax返回的是jqXHR物件
所以jqXHR就需要混入Promise模型
deferred.promise(jqXHR).complete = completeDeferred.add; jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail;
看ajax原始碼前需要了解回到佇列與Deferreds
jqXHR混入了Promise模型處理,當然只能讀防止修改,之外還增加了一個complete的的回撥佇列
具體怎麼使用,看後面
for (i in { success : 1, error : 1, complete : 1 }) { jqXHR[i](s[i]); }
很巧妙的一個處理,把使用者配置檔案中的回撥函式給註冊到這個jqXHR的回撥體系中
所以就把所有的有關回調都繫結到了jqXHR物件上了
ajax提供3種事件通知介面
1 提供全域性事件,外部的檢視可以根據ajax狀態進行改變
2 提供內部事件介面,根據流程做相對應的處理
3 提供鏈式事件介面,通過Promise實現
當ajax開始時模擬全域性事件,ajaxStart
這裡主要利用了jQuery.event.trigger和jQuery.fn.trigger模擬發事件
if (fireGlobals && jQuery.active++ === 0) { // 則通過jQuery.event.trigger模擬觸發 jQuery.event.trigger("ajaxStart"); }
ajax傳送訊息,觸發ajaxSend
/** * 全域性事件ajaxSend * 如果需要,對特定物件觸發全域性事件ajaxSend */ if (fireGlobals) { globalEventContext.trigger("ajaxSend", [jqXHR, s]); }
結束時觸發ajaxSuccess或ajaxError,再出發ajaxComplete,如果全部ajax結束則觸發ajaxStop。
if (fireGlobals) { globalEventContext.trigger(isSuccess ? "ajaxSuccess" : "ajaxError", [jqXHR, s, isSuccess ? success : error]); } // Complete completeDeferred.fireWith(callbackContext, [jqXHR, statusText]); if (fireGlobals) { globalEventContext.trigger("ajaxComplete", [jqXHR, s]); // Handle the global AJAX counter if (!(--jQuery.active)) { jQuery.event.trigger("ajaxStop"); } }
關於快取資料
如果我們的請求為GET的時候的處理,使用者通過data自定義了一些資料,那麼這些資料只能通過拼接成url傳遞給服務端
我們需要通過給地址附加引數_=xxx來避免快取
if (s.cache === false) { s.url = rts.test(cacheURL) ? // If there is already a '_' parameter, set its value cacheURL.replace(rts, "$1_=" + ajax_nonce++) : // Otherwise add one to the end cacheURL + (ajax_rquery.test(cacheURL) ? "&" : "?") + "_=" + ajax_nonce++; }
從jQuery 1.5開始,$.ajax()
返回XMLHttpRequest(jqXHR)物件,該物件是瀏覽器的原生的XMLHttpRequest物件的一個超集。
例如,它包含responseText
和responseXML
屬性,以及一個getResponseHeader()
方法。
當傳輸機制不是是XMLHttpRequest時(例如,一個JSONP請求指令碼,返回一個指令碼 tag 時),jqXHR物件儘可能的模擬原生的XHR功能。
從jQuery 1.5.1開始, jqXHR
物件還包含了overrideMimeType
方法 (它在jQuery 1.4.x中是有效的,但是在jQuery 1.5中暫時的被移除)。
.overrideMimeType()
方法可能用在beforeSend()
的回撥函式中,
例如,修改響應的Content-Type資訊頭:
$.ajax({ url: "http://fiddle.jshell.net/favicon.png", beforeSend: function ( xhr ) { xhr.overrideMimeType("text/plain; charset=x-user-defined"); } }).done(function ( data ) { if( console && console.log ) { console.log("Sample of data:", data.slice(0, 100)); } });
從 jQuery 1.5 開始,$.ajax()
返回的jqXHR物件 實現了 Promise 介面, 使它擁有了 Promise 的所有屬性,方法和行為。(見Deferred object獲取更多資訊)。
為了讓回撥函式的名字統一,便於在$.ajax()
中使用。jqXHR也提供.error()
.success()
和.complete()
方法。這些方法都帶有一個引數,該引數是一個函式,此函式在 $.ajax()
請求結束時被呼叫,並且這個函式接收的引數,與呼叫 $.ajax()
函式時的引數是一致。這將允許你在一次請求時,對多個回撥函式進行賦值,甚至允許你在請求已經完成後,對回撥函式進行賦值(如果該請求已經完成,則回撥函式會被立刻呼叫)。
更多的更多大家還是仔細參考API吧