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

jQuery原始碼分析系列(36) : Ajax

什麼是型別轉化器?

jQuery支援不同格式的資料返回形式,比如dataType為 xml, json,jsonp,script, or html

但是瀏覽器的XMLHttpRequest物件對資料的響應只有 responseText與responseXML 二種

所以現在我要定義dataType為jsonp,那麼所得的最終資料是一個json的鍵值對,所以jQuery內部就會預設幫你完成這個轉化工作

jQuery為了處理這種執行後資料的轉化,就引入了型別轉化器,如果沒有指定型別就依據響應頭Content-Type自動處理

資料傳輸,伺服器只能返回字串形式的,所以如果我們dataType為jsop或者json的時候

伺服器返回的資料為

responseText: "{"a":1,"b":2,"c":3,"d":4,"e":5}"
給轉化成
responseJSON: Object
    {
        a: 1
        b: 2
        c: 3
        d: 4
        e: 5
    }

伺服器的傳輸返回的只能是string型別的資料,但是使用者如果通過jQuery的dataType定義了json的格式後,會預設把資料轉換成Object的形式返回

這就是jQuery內部做的智慧處理了

jQuery內把自定義的dataType與伺服器返回的資料做相對應的對映處理,通過converters儲存對應的處理控制代碼

把需要型別轉換器ajaxConvert在服務端響應成功後,對定義在jQuery. ajaxSettings中的converters進行遍歷,找到與資料型別相匹配的轉換函式,並執行。

converters的對映:

converters: {
           // Convert anything to text、
           // 任意內容轉換為字串
           // window.String 將會在min檔案中被壓縮為 a.String
           "* text": window.String,
 
           // Text to html (true = no transformation)
// 文字轉換為HTML(true表示不需要轉換,直接返回) "text html": true, // Evaluate text as a json expression // 文字轉換為JSON "text json": jQuery.parseJSON, // Parse text as xml // 文字轉換為XML "text xml": jQuery.parseXML }

除此之外還有額外擴充套件的一部分jsonp的處理

// Ajax請求設定預設的值
jQuery.ajaxSetup({
    /**
     * 內容型別傳送請求頭(Content-Type),用於通知伺服器該請求需要接收何種型別的返回結果。
     * 如果accepts設定需要修改,推薦在$.ajaxSetup() 方法中設定一次。
     * @type {Object}
     */
    accepts: {
        script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
    },
    contents: {
        script: /(?:java|ecma)script/
    },
    converters: {
        "text script": function(text) {
            jQuery.globalEval(text);
            return text;
        }
    }
});

所以其格式就是

text –> (html,json,script)的處理了

其寓意就是伺服器返回的用於只是string型別的文字格式,需要轉化成使用者想要的dataType型別的資料

{"* text": window.String, "text html": true, "text json": jQuery.parseJSON, "text xml": jQuery.parseXML}

型別的轉化都是發生在伺服器返回資料後,所以對應的就是ajax 方法中的done之後,當然這個done方法也是經過請求分發器包裝過的,至於為什麼要這樣處理上章就已經提過到了,為了處理正常請求與jsonp的跨域請求的問題

所以當AJAX請求完成後,會呼叫閉包函式done,在done中判斷本次請求是否成功,如果成功就呼叫ajaxConvert對響應的資料進行型別轉換

所以在此之前需要:

1:正確分配dataType型別,如果使用者不設定(空)的情況

2:需要轉化成converters對映表對應的格式比如(* text, text html , text xml , text json)

dataType型別的轉化

dataType型別的引數,可以是xml, json, script, or html 或者乾脆為空,那麼jQuery就需要一個只能的方法去判斷當前是屬於什麼資料處理

因此就引入了

ajaxHandleResponses 處理響應轉化器,解析出正確的dataType型別
response = ajaxHandleResponses(s, jqXHR, responses);

dataType無法就那麼幾種情況

1:dataType為空,自動轉化

此時jQuery只能根據頭部資訊是猜測當前需要處理的型別

// 刪除掉通配dataType,得到返回的Content-Type
while (dataTypes[0] === "*") {
    dataTypes.shift();
    if (ct === undefined) {
        ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
    }
}

通過xhr.getAllResponseHeaders()得到頭部資訊,然後去匹配Content-Type所有物件的值即可

當然找到這個Content-Type = “html”,我們還得看看有沒有對應處理的方法,如果有就需要替換這個dataTypes

// 看看是不是我們能處理的Content-Type,比如圖片這類二進位制型別就不好處理了
if (ct) {
    // 實際上能處理的就是text、xml和json
    for (type in contents) {
        if (contents[type] && contents[type].test(ct)) {
            dataTypes.unshift(type);
            break;
        }
    }
}

經過這個流程後,dataTypes 本來是* 就變成了對應的html了,這是jquery內部的自動轉化過程

2:dataType開發者指定

xml, json, script, html, jsop

總結:

型別轉換器將服務端響應的responseText或responseXML,轉換為請求時指定的資料型別dataType,

如果沒有指定型別就依據響應頭Content-Type自動處理

型別轉換器的執行過程
response = ajaxConvert(s, response, jqXHR, isSuccess);

原始碼部分

function ajaxConvert(s, response, jqXHR, isSuccess) {
    current = dataTypes.shift();
    while (current) {
        if (current) {
            // 如果碰到了*號,即一個任意型別,而轉換為任意型別*沒有意義
            if (current === "*") {
                current = prev;
                // 轉化的重點
                // 如果不是任意的型別,並且找到了一個不同的型別
            } else if (prev !== "*" && prev !== current) {

                // Seek a direct converter
                // 組成對映格式,匹配轉化器
                // * text: function String() { [native code] }
                // script json: function () {
                // text html: true
                // text json: function parse() { [native code] }
                // text script: function (text) {
                // text xml: function (data) {
                conv = converters[prev + " " + current] || converters["* " + current];

                // If none found, seek a pair
                // 假如找不到轉化器
                // jsonp是有瀏覽器執行的呢,還是要呼叫globalEval
                if (!conv) {
                    //...............
                }

                // Apply converter (if not an equivalence)
                // 如果有對應的處理控制代碼,執行轉化
                if (conv !== true) {

                    // Unless errors are allowed to bubble, catch and return them
                    if (conv && s["throws"]) {
                        response = conv(response);
                    } else {
                        try {
                            //執行對應的處理控制代碼,傳入伺服器返回的資料
                            response = conv(response);
                        } catch (e) {
                            return {
                                state: "parsererror",
                                error: conv ? e : "No conversion from " + prev + " to " + current
                            };
                        }
                    }
                }
            }
        }
    }

    return {
        state: "success",
        data: response
    };
}

流程

1.遍歷dataTypes中對應的處理規則【"script","json"】

2.製作jqXHR物件的返回資料介面

  1. json: "responseJSON"
  2. text: "responseText"
  3. xml: "responseXML"

如:jqXHR.responseText: "{"a":1,"b":2,"c":3,"d":4,"e":5}"

3. 生成轉化器對應的匹配規則,尋找合適的處理器

4. 返回處理後的資料response

分析一下特殊的jsonp的轉化流程

先看看轉化對應的處理器

jsonp:

converters["script json"] = function() {
            if (!responseContainer) {
                jQuery.error(callbackName + " was not called");
            }
            return responseContainer[0];
};

jsonp的轉化器只是很簡單的從responseContainer取出了對應的值,所以responseContainer肯定在轉化之後就應該把資料給轉化成陣列物件了

當然做原始碼分析需要一點自己想猜想能力,比如

responseContainer這個陣列物件如何而來?

那麼我們知道jsonp的處理的原理,還是通過載入script,然後伺服器返回一個回撥函式,responseContainer資料就是回撥函式的實參

所以需要滿足responseContainer的處理,必須要先滿足指令碼先載入,所以我們要去分發器中找對應的載入程式碼

首先responseContainer是內部變數,只有一個來源處,在預處理的時候增加一個全域性的臨時函式

然後程式碼肯定是執行了這個函式才能把arguments引數賦給responseContainer

overwritten = window[callbackName];
window[callbackName] = function() {
    responseContainer = arguments;
};

callbcakName是內部建立的一個尼瑪函式名

jQuery203029543792246840894_1403062512436 = function() {
    responseContainer = arguments;
};

我們傳送請求

http://192.168.1.114/yii/demos/test.php?backfunc=jQuery203029543792246840894_1403062512436&action=aaron&_=1403062601515

伺服器那邊就回調後,執行了jQuery203029543792246840894_1403062512436(responseContainer );

所以全域性的callbackName函式需要在分發器中指令碼載入後才能執行,從而才能擷取到伺服器返回的資料

我也不可能每個都分析到位,所以大家有選擇的自己根據需求去看原始碼吧,大體的流程思路理解的,看起來就很快了,至於其餘的型別,在之後遇到了就會在分析了