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物件的返回資料介面
- json: "responseJSON"
- text: "responseText"
- 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函式需要在分發器中指令碼載入後才能執行,從而才能擷取到伺服器返回的資料
我也不可能每個都分析到位,所以大家有選擇的自己根據需求去看原始碼吧,大體的流程思路理解的,看起來就很快了,至於其餘的型別,在之後遇到了就會在分析了