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函數需要在分發器中腳本加載后才能執行,從而才能截取到服務器返回的數據

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM