1. 程式人生 > >jQuery原始碼分析系列(37) : Ajax 總結

jQuery原始碼分析系列(37) : Ajax 總結

綜合前面的分析,我們總結如下3大塊:

  • jQuery1.5以後,AJAX模組提供了三個新的方法用於管理、擴充套件AJAX請求
    1. 前置過濾器 jQuery. ajaxPrefilter
    2. 請求分發器 jQuery. ajaxTransport
    3. 型別轉換器 ajaxConvert
  • 為了整體性與擴充套件性考慮,把整個結構通過Deferred實現非同步鏈式模型,Promise物件可以輕易的繫結成功、失敗、進行中三種狀態的回撥函式,然後通過在狀態碼在來回調不同的函式就行了
  • 出於同源策略考慮,存在跨域問題,所以ajax內部的處理總的來分2大塊
    1. 基於XMLHttpRequest的ajax請求
    2. 基於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的的回撥佇列

image_thumb2

具體怎麼使用,看後面

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物件的一個超集。

例如,它包含responseTextresponseXML屬性,以及一個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吧