jQuery deferred應用之ajax詳細源碼分析(二)

 在上一節中,我只貼出了$.Deferred的源碼分析,並沒用講解怎麼使用它,如今咱們先看看$.ajax是如何使用它,讓咱們進行異步任務的處理。javascript

   若是沒看上節的代碼,請先稍微瞭解一下 jQuery Deferred的工做原理,這樣ajax你才能用得更好。php

   這裏我將以一個跨域請求 "http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"這個js文件爲例,講解jQuery的ajax工做原理,知道了原理,什麼ajax都是浮雲了。html

  先看這張圖片,對$.ajax先來看全局的展望java

ajax

這裏外部有兩個變量   prefilters = {}, transports = {}, 他們將會應爲addToPrefiltersOrTransports() 經過閉包變爲 node

  prefilters={json:[fun],jsonp:[fun],script:[fun]}  transports={*:[fun],script:[fun]} 這裏先不討論這兩個變量,咱們此次用實例開始吧jquery

 

  var req = $.ajax("http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js", 
{ dataType: "script", cache: true, timeout: "10000", statusCode: {200: function () { console.log("request finished and response is ready") },
0: function () { console.log("request wrong")} } }).done(function () { console.log("跨域請求成功") }).success(function () { console.log("跨域請求成功2") });;
req.done(function () { console.log("跨域請求成功3") });

   這裏,設置跨域請求js文件,簡單的參數配置,前面說過jQuery ajax有強大的ajaxSettings參數配置,這裏爲何$.ajax().done().success()呢?其實這個$.ajax()會返回一個jqXHR,而後這個對象ajax

   由於這句代碼deferred.promise(jqXHR),使得jqXHR具備deferred對象的全部只讀方法。詳情見上章,接下來一步步看這個代碼如何執行.json

 

 ajax: function (url, options) {
//若是url是對象的話,冒充1.5版本以前的方法 -->$.ajax({url:"xx",data:{},})
if (typeof url === "object") {
options = url;
url = undefined;
}
//強制設置options成爲一個對象 -->options爲對象
options = options || {};
//這裏建立了ajax的不少私有變量
var //建立最終的ajax參數對象s
s = jQuery.ajaxSetup({}, options), -->經過$.ajaxSetup改變默認的參數配置,返回給s.
// Callbacks context
callbackContext = s.context || s,
// Context for global events
// It's the callbackContext if one was provided in the options
//若是是Dom node或者jQuery collection
globalEventContext = callbackContext !== s &&
(callbackContext.nodeType || callbackContext instanceof jQuery) ?
jQuery(callbackContext) : jQuery.event,
// 建立一個deferred對象
deferred = jQuery.Deferred(), -->使用到了Deferred對象
completeDeferred = jQuery._Deferred(),
// Status-dependent callbacks
//一組數值的HTTP代碼和函數對象,當響應時調用了相應的代碼 如$.ajax({statusCode: {404: function() {alert('page not found');}});
  默認爲空對象
statusCode = s.statusCode || {}, -->這裏咱們傳了一個 statusCode:{200:function(){}..}
// ifModified key
ifModifiedKey,
// 請求頭部 (they are sent all at once)
requestHeaders = {},
requestHeadersNames = {},
// 響應 headers
responseHeadersString,
responseHeaders,
// transport
transport,
//請求超時時間,異步請求下請求多少時間後停止請求,
timeoutTimer, -->timeout:10000
// 判斷是不是跨域請求的變量
parts,
// jqXHR state
state = 0,
// To know if global events are to be dispatched
fireGlobals,
// Loop variable
i,
//僞劣的jqXHR, -->這裏就是返回的jqXHR對象,初始一些key
jqXHR = {

readyState: 0,

/*設置dataType,達到預期服務器返回的數據類型,,jQuery 將自動根據 HTTP 包 MIME 信息來智能判斷
*/
setRequestHeader: function (name, value) {
//這裏狀態還爲0,表示還在發送請求前,設置請求頭
if (!state) {
var lname = name.toLowerCase();
name = requestHeadersNames[lname] = requestHeadersNames[lname] || name;
requestHeaders[name] = value; //私有requestHeadersNames
}
return this;
},

// Raw string
getAllResponseHeaders: function () {
return state === 2 ? responseHeadersString : null;
},

// Builds headers hashtable if needed
getResponseHeader: function (key) {
var match;
if (state === 2) {
if (!responseHeaders) {
responseHeaders = {};
while ((match = rheaders.exec(responseHeadersString))) {
responseHeaders[match[1].toLowerCase()] = match[2];
}
}
match = responseHeaders[key.toLowerCase()];
}
return match === undefined ? null : match;
},

// Overrides response content-type header
overrideMimeType: function (type) {
if (!state) {
s.mimeType = type;
}
return this;
},

// Cancel the request
abort: function (statusText) {
statusText = statusText || "abort";
if (transport) {
transport.abort(statusText);
}
done(0, statusText);
return this;
}
};

/* Callback for when everything is done, jQuery做者喜歡函數裏嵌套函數,局部調用方便
* 2: request received
* 4: request finished and response is ready <---readyState
* 200: "OK"
* 404: Page not found <----status
*/
function done(status, nativeStatusText, responses, headers) { -->這個done函數寫到這個函數的內部,處理不一樣狀態的req,
其實這個寫法改爲var done=function(){}更好
// 請求狀態爲2,表面已經請求過一次,當即返回 -->我是分析到下面回來的 ,第一次請求默認的state=0
if (state === 2) {
return;
}

// State is "done" now
state = 2; -->哦,處理了,爲何是2呢,由於XMLHttpRequest 狀態爲2的意思是
request received


// 若是存在超時請求停止,清楚這個timeout
if (timeoutTimer) { -->timeout有用了,這裏被清除了~_~,我都響應請求了,確定得把你刪了
clearTimeout(timeoutTimer);
}

// Dereference transport for early garbage collection
// (no matter how long the jqXHR object will be used)
transport = undefined;

// Cache response headers
responseHeadersString = headers || "";

// Set readyState
jqXHR.readyState = status > 0 ? 4 : 0;

var isSuccess,
success,
error,
statusText = nativeStatusText,
response = responses ? ajaxHandleResponses(s, jqXHR, responses) : undefined,
lastModified,
etag;

// If successful, handle type chaining
if (status >= 200 && status < 300 || status === 304) { -->服務器成功處理了 咱們的狀態是200

// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if (s.ifModified) {

if ((lastModified = jqXHR.getResponseHeader("Last-Modified"))) {
jQuery.lastModified[ifModifiedKey] = lastModified;
}
if ((etag = jqXHR.getResponseHeader("Etag"))) {
jQuery.etag[ifModifiedKey] = etag;
}
}

// If not modified
if (status === 304) {

statusText = "notmodified";
isSuccess = true;

// If we have data
} else {

try {
success = ajaxConvert(s, response); --> 200狀態,isSucess=true
statusText = "success";
isSuccess = true;
} catch (e) {
// We have a parsererror
statusText = "parsererror";
error = e;
}
}
} else {
// We extract error from statusText
// then normalize statusText and status for non-aborts
error = statusText;
if (!statusText || status) {
statusText = "error";
if (status < 0) {
status = 0;
}
}
}

//設置jqXHR.status 其實也就是XMLHttpRequest狀態
jqXHR.status = status; -->哦,這裏設置了jqXHR的狀態了 200
jqXHR.statusText = "" + (nativeStatusText || statusText);

// 成功請求 resolveWith
if (isSuccess) {
deferred.resolveWith(callbackContext, [success, statusText, jqXHR]);
->成功了,固然得resolveWith,是否是發現done或者
success進來的函數一個個執行了^_^
} else { //失敗請求 rejectWith
->resolve意味着什麼?意味後面無論你done().done() 多少都會被直接觸發 ~_~
deferred.rejectWith(callbackContext, [jqXHR, statusText, error]);
}

// 狀態參數代碼,默認爲空
jqXHR.statusCode(statusCode);
statusCode = undefined;

if (fireGlobals) {
globalEventContext.trigger("ajax" + (isSuccess ? "Success" : "Error"),
[jqXHR, s, isSuccess ? success : error]);
}

// 無論請求成功與否都執行的complete resolveWith
completeDeferred.resolveWith(callbackContext, [jqXHR, statusText]);
-->原來$.ajax().complete在這裏調用,因此無論成功沒有都resolve了

if (fireGlobals) {
globalEventContext.trigger("ajaxComplete", [jqXHR, s]);
// Handle the global AJAX counter
if (!(--jQuery.active)) {
jQuery.event.trigger("ajaxStop");
}
}
}

// 給這個jqXHR對象附上deferred對象的只讀方法,包括done,always,isResolved等等,這裏代表了,爲何$.ajax事後
//咱們可使用sucess,error,complete方法
deferred.promise(jqXHR);
jqXHR.success = jqXHR.done; -->做者爲了讓咱們更好的調用,又多了3個藉口,哎,其實能夠直接不須要的
jqXHR.error = jqXHR.fail;
jqXHR.complete = completeDeferred.done;

// Status-dependent callbacks 其實這個參數變量不多用,不過還能夠,目前能夠觸發兩個狀態的statusCode: {200: function ()
//{ console.log("request ok") }, 404: function () { console.log("page not found")} }
jqXHR.statusCode = function (map) { --> map={200:"..",0:".."}
if (map) {
var tmp; //臨時函數變量
if (state < 2) {
for (tmp in map) {
statusCode[tmp] = [statusCode[tmp], map[tmp]];
}
} else {
tmp = map[jqXHR.status]; //jqXHR返回相應的callback,而後調用then方法觸發
--> 咱們的狀態代碼在這裏調用,經過jqXHR.then方法調用

jqXHR.then(tmp, tmp);
}
}
return this;
};

// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
// We also use the url parameter if available
// Remove 將url的hash去掉 rhash=/#.*$/ rprotocol = /^\/\// ajaxLocParts[1]="http:"
s.url = ((url || s.url) + "").replace(rhash, "").replace(rprotocol, ajaxLocParts[1] + "//");

// 取出dataTypes list,不存在則爲* rspacesAjax = /\s+/ 如:s.dataType=[*]
s.dataTypes = jQuery.trim(s.dataType || "*").toLowerCase().split(rspacesAjax);

//默認設置crossDomain 同域請求爲false, 跨域請求爲true,若是想強制跨域請求(如JSONP形式)同一域,設置crossDomain爲true
if (s.crossDomain == null) {
//同ajaxLocParts變量,這裏也是經過這個請求的url來判斷是不是跨域請求
parts = rurl.exec(s.url.toLowerCase());
//這裏來判斷是不是跨域請求,又是!! 主要判斷請求的url parts與 ajaxLocParts -->這裏智能判斷你的請求地址是不是跨越,很
牛氣吧,這裏咱們的corssDomain爲true

s.crossDomain = !!(parts &&
(parts[1] != ajaxLocParts[1] || parts[2] != ajaxLocParts[2] ||
(parts[3] || (parts[1] === "http:" ? 80 : 443)) !=
(ajaxLocParts[3] || (ajaxLocParts[1] === "http:" ? 80 : 443)))
);
}

//若是存在data參數而且 processData=true&&s.data爲一個對象吧 -->咱們沒傳data參數,processData參數默認爲true,
表示默認序列化參數data
if (s.data && s.processData && typeof s.data !== "string") {
//調用序列化函數
s.data = jQuery.param(s.data, s.traditional);
}

// Apply prefilters
inspectPrefiltersOrTransports(prefilters, s, options, jqXHR); -->發送請求前,調用過濾器將

// If request was aborted inside a prefiler, stop there
if (state === 2) {
return false;
}

// We can fire global events as of now if asked to
fireGlobals = s.global;

// Uppercase the type
s.type = s.type.toUpperCase();

// Determine if request has content
//rnoContent= /^(?:GET|HEAD)$/
s.hasContent = !rnoContent.test(s.type);

// Watch for a new set of requests
if (fireGlobals && jQuery.active++ === 0) {
jQuery.event.trigger("ajaxStart");
}

// More options handling for requests with no content
if (!s.hasContent) {

//若是data參數存在,則append to url
if (s.data) { -->咱們之前是否是喜歡xx.php?data="xx"&&.. 這裏會把序列化
後的data傳到問號後面,很簡單吧
//rquery = /\?/請求的url是否存在"?" 就好比請求"user.php?" -->這就是爲何data參數會被傳到url?後面


s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
// #9682: remove data so that it's not used in an eventual retry
delete s.data;
}

// Get ifModifiedKey before adding the anti-cache parameter
ifModifiedKey = s.url;

// Add anti-cache in url if needed
if (s.cache === false) {

var ts = jQuery.now(),
// try replacing _= if it is there
ret = s.url.replace(rts, "$1_=" + ts);

// if nothing was replaced, add timestamp to the end
s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
}
}

// Set the correct header, if data is being sent
if (s.data && s.hasContent && s.contentType !== false || options.contentType) {
jqXHR.setRequestHeader("Content-Type", s.contentType);
}

// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if (s.ifModified) {
ifModifiedKey = ifModifiedKey || s.url;
if (jQuery.lastModified[ifModifiedKey]) {
jqXHR.setRequestHeader("If-Modified-Since", jQuery.lastModified[ifModifiedKey]);
}
if (jQuery.etag[ifModifiedKey]) {
jqXHR.setRequestHeader("If-None-Match", jQuery.etag[ifModifiedKey]);
}



}


/*設置dataType,達到預期服務器返回的數據類型,若是沒有dataType參數,jQuery 將自動根據 HTTP 包 MIME 信息來智能判斷
默認的s.accepts= accepts: {xml: "application/xml, text/xml",
html: "text/html",text: "text/plain",json: "application/json, text/javascript","*": allTypes}
allTypes= ['*\/']+['*'] 若是設置了dataType參數,即 s.dataTypes = jQuery.trim(s.dataType || "*").toLowerCase().split(/\s+/);
好比:$(url,{dataType:"json"}) 這裏第二個參數將成爲 "application/json, text/javascript"+",*\/*;q=0.01"
*/
jqXHR.setRequestHeader(
"Accept",
s.dataTypes[0] && s.accepts[s.dataTypes[0]] ?
s.accepts[s.dataTypes[0]] + (s.dataTypes[0] !== "*" ? ", " + allTypes + "; q=0.01" : "") :
s.accepts["*"]
);
//檢查頭部是不是指了參數 s.headers={} 一個額外的"{鍵:值}"對映射到請求一塊兒發送 -->咱們沒設置頭部
for (i in s.headers) {
jqXHR.setRequestHeader(i, s.headers[i]);
}

// Allow custom headers/mimetypes and early abort
if (s.beforeSend && (s.beforeSend.call(callbackContext, jqXHR, s) === false || state === 2)) {
// Abort if not done already
jqXHR.abort();
return false;

}

//$.ajax().success(callback).error(callback).complete(callback) 在這裏增長callback,這裏表面如今的ajax請求能夠不止一個回調函數
for (i in { success: 1, error: 1, complete: 1 }) {
jqXHR[i](s[i]);
}

// Get transport
transport = inspectPrefiltersOrTransports(transports, s, options, jqXHR);

// If no transport, we auto-abort
if (!transport) {
done(-1, "No Transport");
} else {
jqXHR.readyState = 1;
// Send global event
if (fireGlobals) {
globalEventContext.trigger("ajaxSend", [jqXHR, s]);
}
// Timeout設置請求超時時間(毫秒),超時請求後,jqXHR.abort() 停止請求,簡單的意思就是在指定的時間內還未響應,中止請求
if (s.async && s.timeout > 0) {
timeoutTimer = setTimeout(function () { -
->這裏知道咱們爲何設置timeout了吧,就是若是在timeout時間內還沒請求到數據
jqXHR.abort("timeout"); 確定得中止了,如何中止的,得看abort.提早是咱們設置了這個參數而且是異步請求
}, s.timeout);
}

try {
state = 1;
transport.send(requestHeaders, done);
-->這個方法就能知道jQuery是如何異步跨越請求這個js文件的,見下面分析
} catch (e) {
// Propagate exception as error if not done
if (state < 2) { -->處理上面這個方法的異常,萬一你傳得url不存在呢。。。
done(-1, e);
// Simply rethrow otherwise
} else {
jQuery.error(e);
}
}
}

return jqXHR; -->分析到這裏,怎麼辦了?其實看出來,咱們只分析了$.ajax()並沒用分析後面那段鏈式操做,
}, 還有js文件怎麼請求的,請求到哪兒了?先回到上面的done方法


     上面代碼的-->指出了這段代碼關鍵的運行,以及爲何咱們能夠直接$.ajax().done().done()異步處理任務,並且能夠同時增長多個回調函數,這多得多虧$.Deferred().api

     好吧,重點來分析怎麼transport.send(requestHeaders,done)來跨域請求咱們的js文件吧...怎麼jQuery一個ajax這麼複雜?還不是爲了迎合上千萬使用者不一樣的使用目的。也許對你來講,跨域

    你只須要一個簡單的ajax請求,我才無論他跨域了沒有!這裏就是jQuery的一個缺點吧,木辦法啊,大家怎麼多使用者,我要每一個都照顧啊!好吧,來分析js怎麼請求的吧

   

 /* 綁定script分發器,經過在header中建立script標籤異步載入js.                   -->看到這裏,你會有點了解這個transports分發器在幹什麼了,
誰叫你要請求js文件啊,仍是跨域的,我平時就請求一個.php,沒想這麼多~_~,
原來ajax能夠請求這麼多東西
*/
jQuery.ajaxTransport("script", function (s) {

if (s.crossDomain) { -->我但是跨域請求哦

var script,
head = document.head || document.getElementsByTagName("head")[0] || document.documentElement; //一步步的獲取head元素

return {
/*如下便是異步加載js的經常使用方法
*/
send: function (_, callback) { -->尼瑪的send方法在這裏,藏得太深了

script = document.createElement("script"); -->好吧,看到這裏,你以爲是否是忽然jQuery裏怎麼會有這麼簡單的代碼了?

script.async = "async";

if (s.scriptCharset) {
script.charset = s.scriptCharset;
}

script.src = s.url;

// script加載完成觸發的callback
script.onload = script.onreadystatechange = function (_, isAbort) { -->js文件加載後的callback
//停止script回調請求或者不存在readyState|| load|complete狀態
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {

//處理ie下得內存泄露問題 ,onload事件觸發後,銷燬事件.
script.onload = script.onreadystatechange = null;

if (head && script.parentNode) { ->head.removeChild(script)?你把我才加載進來的js文件刪了
幹嗎啊,本身想吧
head.removeChild(script); //加載事後刪除script標籤元素
}

// 註銷script變量
script = undefined; ->真把加載進來的script清的一乾而盡,看來只能跨域請求js,而後執行一次callback而已,
->有點划不來吧 ~_~

// 若是沒有被停止調用callback
if (!isAbort) {
callback(200, "success"); ->這個callback是神馬呢?就是上面的done方法,而後這個done方法裏面根據不
同的status進行不一樣的請求,你沒忘吧!
}
}
};
//把建立的script加入head頭部,可是加載完成後會被清除該script標籤
head.insertBefore(script, head.firstChild);
-->把咱們請求的js文件加到頭部,不加到頭部沒辦法執行裏面的函數 ~_~,
執行了又把人家刪了
},
/*手動停止script請求
*/
abort: function () { --> 好吧,咱們沒有abort
if (script) {
script.onload(0, 1);
}
}
};
}
});


這只是一個簡單的跨域請求js文件,怎麼這麼多代碼?哎,這個jQuery ajax的參數配置太多了,他會不停的if再if ,這些if造就了jQuery的調用接口這麼靈活了,能夠這麼寫,也能夠那麼寫,知足不一樣

的人的口味。 分析到這裏,可能你已經頭很大了,木辦法,它確實很難,這只是ajax的一部分。

   好吧,我認可上面都是低級$.ajax方法,爲了知足調用者使用更簡單,做者提供了$.get() $.post(),$.getScript(),$.getJSON(),哎,一切爲了減小使用者的負擔啊,原本是不必加這些代碼的

  仍是來看看,這幾個方法怎麼基於$.ajax寫出來的,其實很簡單了,若是你真真看懂了$.ajax ~_~

 

 /*這裏遍歷數組擴展$.get()   $.post();其實都仍是調用的$.ajax,只是type不一樣  *  這裏$.ajax仍是返回jqXHR,它與之前版本不一樣之處是它是超集的XMLHTTPRequest,因爲deferred.promise(jqXHR);使這個jqXHR多了一些done,fail,always方法,  * 固然也能夠success,fail,complete其實這些方法就是done,fail,always,詳情可見$.ajax和$.Deferred對象  */    jQuery.each(["get", "post"], function (i, method) {                               -->老生常談得遍歷數組,擴展$.method ,                                                                                         之後我必定幫大家分析下jQuery核心庫代碼^_^        jQuery[method] = function (url, data, callback, type) {            //這裏就是$.get("url",function(){})式的調用方法            if (jQuery.isFunction(data)) {  //統一成$.get("url",data,function(){})                type = type || callback;                callback = data;                data = undefined;      //把第二個參數設置成undefined,其目的也是是$.get()的接口靈活,                                       //從這裏能夠看出jQuery的調用接口靈活仍是基於強大的if判斷語句            }            //這裏返回1.5版本以前的ajax調用方式            return jQuery.ajax({                                                     -->搞來搞去仍是$.ajax,只是幫你設置了參數了                type: method, //get or post                    url: url,                data: data,                success: callback,                dataType: type            });        };    });        getScript: function (url, callback) {               ->尼瑪的,仍是調用的get,話說這個方法能夠用了動態加載js文件,也能夠按需加載哦,                                                            至關於jQuery的js加載器吧,好扯的加載器,                                                                                       return jQuery.get(url, undefined, callback, "script");        },        //json文件,全部在JSON字符串表示,不管是屬性或值,必須用雙引號括起來,如{"key1":"value1","key2",:"value2"}保存在一個test.json文件中        //$.getJSON("test.json",function(data){$.each(data,function(key,value){})});      getJSON: function (url, data, callback) {            return jQuery.get(url, data, callback, "json");                                  },
相關文章
相關標籤/搜索