這所有的最終都是通過jQuery.ajax()來完成的。 ajax : function(s) { //兩次繼承s,以便在測試中能檢測 s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); ① var jsonp, jsre = /=\?(&|$)/g, status, data, type = s.type .toUpperCase(); // 如果不是字符集串就轉(zhuǎn)換在查詢字符集串 if (s.data && s.processData && typeof s.data != "string") s.data = jQuery.param(s.data); // 構(gòu)建jsonp請求字符集串。jsonp是跨域請求,要加上callback=?后面將會加函數(shù)名 if (s.dataType == "jsonp") { ② if (type == "GET") {//使get的url包含 callback=?后面將會進行加函數(shù)名 if (!s.url.match(jsre)) s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?"; } // 構(gòu)建新的s.data,使其包含callback=function name else if (!s.data || !s.data.match(jsre)) s.data = (s.data ? s.data + "&" : "") + (s.jsonp||"callback")+ "=?"; s.dataType = "json"; } //判斷是否為jsonp,如果是,進行處理。 if (s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre))) {③ jsonp = "jsonp" + jsc++; / /為請求字符集串的callback=加上生成回調(diào)函數(shù)名 if (s.data)s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); s.url = s.url.replace(jsre, "=" + jsonp + "$1"); // 我們需要保證jsonp 類型響應(yīng)能正確地執(zhí)行 //jsonp的類型必須為script。這樣才能執(zhí)行服務(wù)器返回的 //代碼。這里就是調(diào)用這個回調(diào)函數(shù)。 s.dataType = "script"; //window下注冊一個jsonp回調(diào)函數(shù)有,讓ajax請求返回的代碼調(diào)用執(zhí)行它, //在服務(wù)器端我們生成的代碼 如callbackname(data);形式傳入data. window[jsonp] = function(tmp) { data = tmp;success();complete(); // 垃圾回收,釋放聯(lián)變量,刪除jsonp的對象,除去head中加的script元素 window[jsonp] = undefined; try { delete window[jsonp]; } catch (e) { } if (head) head.removeChild(script); }; } if (s.dataType == "script" && s.cache == null) s.cache = false; // 加上時間戳,可見加cache:false就會加上時間戳 if (s.cache === false && type == "GET") { var ts = now(); var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2"); // 沒有代替,就追加在url的尾部 s.url = ret+ ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : ""); } // data有效,追加到get類型的url上去 if (s.data && type == "GET") { s.url += (s.url.match(/\?/) ? "&" : "?") + s.data; // 防止IE會重復(fù)發(fā)送get和post data s.data = null; } // 監(jiān)聽一個新的請求 if (s.global && !jQuery.active++) jQuery.event.trigger("ajaxStart");④ // 監(jiān)聽一個絕對的url,和保存domain var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec(s.url); // 如果我們正在請求一個遠程文檔和正在load json或script通過get類型 //location是window的屬性,通過和地址欄中的地址比較判斷是不是跨域。 if (s.dataType == "script" && type == "GET"&& parts && (parts[1] && parts[1] != location.protocol || parts[2] != location.host)) {⑤ // 在head中加上<script src=""></script> var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); script.src = s.url; if (s.scriptCharset) script.charset = s.scriptCharset; //如果datatype不是jsonp,但是url卻是跨域的。采用scriptr的 //onload或onreadystatechange事件來觸發(fā)回調(diào)函數(shù)。 if (!jsonp) { var done = false; // 對所有瀏覽器都加上處理器 script.onload = script.onreadystatechange = function() { if (!done&& (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) { done = true; success(); complete();head.removeChild(script); } }; } head.appendChild(script); // 已經(jīng)使用了script 元素注射來處理所有的事情 return undefined; } var requestDone = false; // 創(chuàng)建request,IE7不能通過XMLHttpRequest來完成,只能通過ActiveXObject var xhr = window.ActiveXObject ⑥ new ActiveXObject("Microsoft.XMLHTTP"): new XMLHttpRequest(); // 創(chuàng)建一個請求的連接,在opera中如果用戶名為null會彈出login窗口中。 if (s.username)xhr.open(type, s.url, s.async, s.username, s.password); else xhr.open(type, s.url, s.async); // try/catch是為防止FF3在跨域請求時報錯 try {// 設(shè)定Content-Type ⑦ if (s.data) xhr.setRequestHeader("Content-Type", s.contentType); // 設(shè)定If-Modified-Since if (s.ifModified) xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]|| "Thu, 01 Jan 1970 00:00:00 GMT"); // 這里是為了讓服務(wù)器能判斷這個請求是XMLHttpRequest xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); // 設(shè)定 Accepts header 。指能接收的content-type,在服務(wù)器端設(shè)定 xhr.setRequestHeader("Accept", s.dataType && s.accepts[s.dataType] s.accepts[s.dataType] + ", */*": s.accepts._default); } catch (e) {} //攔截方法,我們可以在send之前進行攔截。返回false就不send if (s.beforeSend && s.beforeSend(xhr, s) === false) { ⑧ // 清除active 請求計數(shù) s.global && jQuery.active--; xhr.abort(); return false; } // 觸發(fā)全局的ajaxSend事件 if (s.global) jQuery.event.trigger("ajaxSend", [xhr, s]); // 等待response返回,主要是為后面setInterval用。 var onreadystatechange = function(isTimeout) { ⑨ // 接收成功或請求超時 if (!requestDone && xhr&& (xhr.readyState == 4 ||isTimeout == "timeout")) { requestDone = true; //清除定時器 if (ival) {clearInterval(ival); ival = null; } // 分析status:tiemout-->error-->notmodified-->success status = isTimeout == "timeout" "timeout" : !jQuery ttpSuccess(xhr) ? "error" : s.ifModified&& jQuery. httpNotModified(xhr, s.url) ? "notmodified": "success"; //如果success且返回了數(shù)據(jù),那么分析這些數(shù)據(jù) if (status == "success") { try { data = jQuery.httpData(xhr, s.dataType, s); } catch (e) { status = "parsererror"; } } // 分析數(shù)據(jù)成功之后,進行l(wèi)ast-modified和success的處理。 if (status == "success") { var modRes; try {modRes = xhr.getResponseHeader("Last-Modified"); } catch (e) { //FF中如果head取不到,會拋出異常} //保存last-mordified的標識。 if (s.ifModified && modRes)jQuery.lastModified[s.url] = modRes; // JSONP 有自己的callback if (!jsonp) success(); } else // 失敗時的處理 jQuery.handleError(s, xhr, status); // 無論如何都進行cpmplate.timeout和接收成功 complete(); if (s.async) xhr = null; // 防內(nèi)存泄漏 } }; if (s.async) { // 這里是采用poll的方式,不是push的方式 //這里為什么不采用onreadystatechange? var ival = setInterval(onreadystatechange, 13); //如果過了timeout還沒有請求到,會中斷請求的。 if (s.timeout > 0) setTimeout(function() { if (xhr) { xhr.abort(); if (!requestDone) onreadystatechange("timeout"); } }, s.timeout); } // 發(fā)送 try {xhr.send(s.data); catch(e){jQuery.handleError(s,xhr,null,e);} ⑩ // firefox 1.5 doesn't fire statechange for sync requests if (!s.async) onreadystatechange(); function success() { // 調(diào)用構(gòu)建請求對象時指定的success回調(diào)。 if (s.success) s.success(data, status); // 執(zhí)行全局的回調(diào) if (s.global) jQuery.event.trigger("ajaxSuccess", [xhr, s]); } function complete() { // 本地的回調(diào) if (s.complete) s.complete(xhr, status); // 執(zhí)行全局的回調(diào) if (s.global) jQuery.event.trigger("ajaxComplete", [xhr, s]); // 全局的ajax計數(shù)器 if (s.global && !--jQuery.active)jQuery.event.trigger("ajaxStop"); } // return XMLHttpRequest便進行about()或其它操作. return xhr; }, Jquery.ajax是大包大攬的非常復(fù)雜的一個方法。它并沒有像其它的lib一樣,把每個小部分都分開來。它是整個都整在一個函數(shù)中。看起來很多,實際上上也沒有脫離前面所說的ajax的請求的五步。它的很大一部分代碼在處理跨域請求的處理上。下面就分別就ajax的代碼進行分析。 ajaxSettings 在①處通過繼承的方式把傳入?yún)?shù)s和默認的jQuery.ajaxSettings都clone到s變量中。S的同名屬性會覆蓋jQuery.ajaxSettings的同名屬性。這里兩次繼承s,以便在測試中能檢測。 //默認的ajax的請求參數(shù) ajaxSettings : { url : location.href,//默認是地址欄中url global : true,//默認支持全局的ajax事件 type : "GET", timeout : 0, contentType : "application/x-www-form-urlencoded", processData : true, async : true, data : null, username : null, password : null, accepts : { xml : "application/xml, text/xml", html : "text/html", script : "text/javascript, application/javascript", json : "application/json, text/javascript", text : "text/plain", _default : "*/*" } 這是默認的ajax的設(shè)定,我們要在參數(shù)s設(shè)定同名的屬性來覆蓋這些屬性。但是我們不能覆蓋accepts。這個會在后面的代碼用到。我們可以通過設(shè)定s.dataType等于accepts中的某一個屬性key指定請求的data類型,如xml,html,script,json,text。dataType還支持默認的_default和跨域的jsonp。不過其最終會解析成script。 scriptTag ②~⑥是處理跨域請求的部分。對于dataType為jsonp的類型,給其請求的字符串(可能是s.data)加上callback=callbackfn的key/value串,然后在window下注冊一個callbackfn的函數(shù)。這個函數(shù)的形式如callbackfn(data){ data = tmp;success();complete();}。它代理了通過ajax(s)的傳入s參數(shù)中success();complete()的功能。它就是調(diào)用這個函數(shù),實際上是調(diào)用success();complete()的函數(shù)。 那么怎么調(diào)用呢?ajax不支持跨域。在⑤處,我們可以看到這里是采用scriptTag的方式來完成。先在頁面的<head>中添加一個<script src=url />的標簽。因為在<head>中。瀏覽器會自動載入并運行請求返回的script。如果是jsonp的形式,服務(wù)器端還要動態(tài)生成的content-type為script的代碼:callbackfn(data);只有這樣才會調(diào)用在window中注冊的函數(shù)callbackfn。同時傳入所需要的參數(shù)。 如dataType == "script"形式的跨域,那只能是通過script.onload 或 script.onreadystatechange事件來觸發(fā)回調(diào)。這里我們可以通過服務(wù)器返回的script代碼:var data=xxx。來傳遞參數(shù)給s.success();s.complete()。Jquery這里采用是全局變量data來進行操作的。 Ajax Event ④是采用了jQuery.event.trigger("ajaxStart");來觸發(fā)全局的ajaxStart事件。這也是說只要注冊了這個事件的元素,在任何的ajax的請求時ajaxStart都會執(zhí)行元素注冊的事件處理函數(shù)。這和Ext的事件有點相似。但是它不是全局的。 jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i, o) { jQuery.fn[o] = function(f) {// f:function return this.bind(o, f); }; 上面的代碼是為jquery對象注冊了ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend這幾種ajax的事件方法,在jquery.ajax中不同的時刻都會觸發(fā)這些事件。當然我們也可以采用s.global=false來設(shè)定不觸發(fā)這些事件。 因為這是全局的,個人認為其設(shè)計的目的就是為了在這些時候能以某種形式來告訴用戶ajax的進行的狀態(tài)。如在ajaxstart的時候,我們可能通過一個topest的div層(加上遮罩的效果)的元素注冊一個ajaxstart事件的處理方法。該方法就是顯示這個層和顯示“你的數(shù)據(jù)正在提交。。?!边@個的提示。這是這6種事件的最佳用法了。 如果進行私有處理,那么要在事件的處理函數(shù)中進行判斷。因為每個事件處理函數(shù)的第二參數(shù)是jquery.ajax(s)的s參數(shù)。我們可以在這個參數(shù)中做私有的標識,如eventType:xxx。每類不同的請求有不同的eventType值。在事件處理函數(shù)再根據(jù)這個eventType==xxx進行判斷,從而進行私有的處理。如果有大量的這樣的私有處理也是會影響ajax的效率的。 setRequestHeader ⑥處是創(chuàng)建一個xhr對象并通過open來創(chuàng)建一個連接(socket)。 ⑦處是設(shè)定請求的頭部(setRequestHeader)。如果data的存在的話,那就得設(shè)定Content-Type,便于服務(wù)器按一定的規(guī)則來解碼。可以看出post的方式通過data傳遞數(shù)據(jù)要安全一點。 那么服務(wù)器如果區(qū)別這個請求是ajax呢?因為同步和異步ajax的請求的頭文件是一樣的。我們?nèi)绻ㄟ^X-Requested-With"="XMLHttpRequest”來標識這個請求是ajax的請求。如果服務(wù)器硬是要區(qū)分的話,就可以通過獲取該頭部來判斷。 在頭部的定義中,還可能通過Accept來指定接受的數(shù)據(jù)的類型,如application/xml, text/xml", "text/html", "text/javascript, 等等。 頭部還有一個If-Modified-Since的屬性用來提高效率的。它和”Last-Modified配合起來使用。在瀏覽器第一次請求某一個URL時,服務(wù)器端的返回狀態(tài)會是200,內(nèi)容是你請求的資源,同時有一個Last-Modified的屬性標記此文件在服務(wù)期端最后被修改的時間,格式類似這樣:Last-Modified: Fri, 12 May 2006 18:53:33 GMT 客戶端第二次請求此URL時,根據(jù) HTTP 協(xié)議的規(guī)定,瀏覽器會向服務(wù)器傳送 If-Modified-Since 報頭,詢問該時間之后文件是否有被修改過: If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT 如果服務(wù)器端的資源沒有變化,則自動返回 HTTP 304 (Not Changed.)狀態(tài)碼,內(nèi)容為空,這樣就節(jié)省了傳輸數(shù)據(jù)量。 當服務(wù)器端代碼發(fā)生改變或者重啟服務(wù)器時,則重新發(fā)出資源,返回和第一次請求時類似。從而保證不向客戶端重復(fù)發(fā)出資源,也保證當服務(wù)器有變化時,客戶端能夠得到最新的資源。 攔截處理 ⑧處是一個send之前的攔截處理,可以通過s. beforeSend(xhr, s)函數(shù)的形式傳入攔截函數(shù)。保證在發(fā)送之前確保滿足某些條件。在取得返回數(shù)據(jù)的時候,也可以通過s.dataFilter(data, type);形式來攔截處理data。不過這里主要的作用對data進一步的篩選。 onreadystatechange ⑨處是onreadystatechange的回調(diào)處理。這里采用是poll的形式進行處理。它把返回的狀態(tài)分成status:tiemout-->error-->notmodified-->success—>parsererror這幾種。如果status == "success"那么分析這些數(shù)據(jù)之后再進行l(wèi)ast-modified相關(guān)的處理。為了不取回沒有修改過數(shù)據(jù)。 分析數(shù)據(jù)的代碼如下: //處理請求返回的數(shù)據(jù) httpData : function(xhr, type, s) { var ct = xhr.getResponseHeader("content-type"), xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0, data = xml? xhr.responseXML : xhr.responseText; if (xml && data.documentElement.tagName == "parsererror") throw "parsererror"; //允許一個pre-filtering函數(shù)清潔repsonse if (s && s.dataFilter) data = s.dataFilter(data, type); //script時,就運行 if (type == "script") jQuery.globalEval(data); //json,生成json對象。 if (type == "json") data = eval("(" + data + ")"); return data; }, 如果返回的content-type是xml,html,text等都返回。對script執(zhí)行jQuery.globalEval來執(zhí)行它。對于Json類型,通過eval來生成返回的json對象。 // 在全局的范圍eval 代碼,也就是在<head></head>中 globalEval : function(data) { data = jQuery.trim(data); if (data) { // Inspired by code by Andrea Giammarchi // http://webreflection.blogspot.com/2007/08/ //global-scope-evaluation-and-dom.html var head = document.getElementsByTagName("head")[0] || document.documentElement, script = document.createElement("script"); script.type = "text/javascript"; if (jQuery.browser.msie) script.text = data; else script.appendChild(document.createTextNode(data)); // Use insertBefore instead of appendChild to circumvent an IE6 // bug. This arises when a base node is used (#2709). head.insertBefore(script, head.firstChild); head.removeChild(script); } },
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請
點擊舉報。