程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> JSP編程 >> 關於JSP >> jquery.ajax源碼

jquery.ajax源碼

日期:2017/1/18 10:31:26      編輯:關於JSP

框架的作用就是簡化我們做的事情,卻又不失靈活性。jquery是js框架中的中流砥柱,靈活並且強大。
 
jquery中對ajax的封裝很完美,且不說底層的ajax函數的強大,但是其上層的get ,post ,getscript ,getJson 基本對各種應用游刃有余。為什麼要看源碼,一是閒著蛋疼,二是為了在出問題時,能找出問題所在。三是……。
jquery中有一個對象ajaxSeetings ,ajax請求最基本的配置就在這裡,結構如下
ajaxSettings: { url: location.href, global: true, type: "GET", contentType: "application/x-www-form-urlencoded", processData: true, async: true, /* timeout: 0, data: null, username: null, password: null, traditional: false, */ // Create the request object; Microsoft failed to properly // implement the XMLHttpRequest in IE7 (can't request local files), // so we use the ActiveXObject when it is available // This function can be overriden by calling jQuery.ajaxSetup xhr: window.XMLHttpRequest && (window.location.protocol !== "file:" || !window.ActiveXObject) ? function() { return new window.XMLHttpRequest(); } : function() { try { return new window.ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {} }, accepts: { xml: "application/xml, text/xml", html: "text/html", script: "text/javascript, application/javascript", json: "application/json, text/javascript", text: "text/plain", _default: "*/*" } }
基本上名字就能代表它的配置項目,processData可能比較陌生。我們在使用get以及其他上層函數請求資源時,傳遞一個key/value的對象。例如$.get(“xxxx”,{name:’pr’,password:’pr’} ,  ……); 如果process設置為true,{name:’pr’,password:’pr’}就會轉換為name=pr&password=pr;這樣在後面如果ajax方式為get則會將裝換的字符串附加到url後面,如果設置為false則不進行此轉換,默認是true,也最好不要改。值得一看內容當然是屬性xhr,這個屬性是個函數,當然函數最後都會返回浏覽器兼容的XmlHttpRequest對象。整個ajax的核心操作對象就是它,這就免去了我們自己構造XmlHttpRequest對象時考慮兼容問題的糾結。
ajax: function( origSettings ),ajax接受一個配置對象,就跟上面的ajaxSettings那樣的結構,
var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings); var jsonp, status, data, callbackContext = origSettings && origSettings.context || s, type = s.type.toUpperCase();   // convert data if not already a string if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional ); }
首先函數將默認配置和傳進來的配置進行合並,在函數中注意有個{},這樣合並就不會影響ajaxSettings 和originSettings的本來的值。CallbackContext是執行ajax回調函數是函數的上下文。其他不多說。然後根據data,ProcessData 和data是否是string來決定是不是要將data對象轉換為參數形式字符串。jquery.param是個工具函數,traditional用來決定是不是要進行深層次遍歷以生成參數字符串。具體事例見jquery文檔。
// Handle JSONP Parameter Callbacks if ( s.dataType === "jsonp" ) { if ( type === "GET" ) { if ( !jsre.test( s.url ) ) { s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?"; } } else if ( !s.data || !jsre.test(s.data) ) { s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; } s.dataType = "json"; } // Build temporary JSONP function if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) { jsonp = s.jsonpCallback || ("jsonp" + jsc++); // Replace the =? sequence both in the query string and the data if ( s.data ) { s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); } s.url = s.url.replace(jsre, "=" + jsonp + "$1"); // We need to make sure // that a JSONP style response is executed properly s.dataType = "script"; // Handle JSONP-style loading window[ jsonp ] = window[ jsonp ] || function( tmp ) { data = tmp; success(); complete(); // Garbage collect window[ jsonp ] = undefined; try { delete window[ jsonp ]; } catch(e) {} if ( head ) { head.removeChild( script ); } }; }
接下來特殊處理請求數據類型為jsonp,jsonp其實是一種非官方的協議,主要就是跨域的訪問。在後面你可以看到它會和請求類型為‘script’相似處理。
如果jsonp大家不熟悉的話,可以去網上查找相關資料,或直接跳過jsonp,不影響後面的閱讀。
jsre是一個正則表達式  jsre = /=\?(&|$)/  ,他主要又來匹配‘=?’這個其實是jsonp請求中獨有的字符串,如果url中沒有對應的字符,則在後面加上jsonp請求獨有的字符串。requery同樣也是一個正則表達式/\?/ ,就是用來匹配問號的,如果原先url含有?,則說明url中帶有參數,則連接字符使用&,否則用?。如果配置對象中含有jsonp,則指定了jsonp的回調函數名,否則使用默認回調函數名callback。若果ajax請求采用post方式,則只需對配置對象中的data進行拼接字符串即可。datatype設置為json是為了進一步的細化處理。無論是在get方式還是其他方式,在處理前都會用jsre匹配一下,只有在確保字符串中沒有jsonp的特征字符串’=?‘時才會就行處理,也就是說不能有兩個jsonp在一起(自己的一點瞎想,歡迎大家討論)。
接下來構建jsonp回調函數。因為前文說過沒有指定jsonp屬性的話是默認為Callback。如果指定了jsonpCallback則直接用但是沒有的就要構造構造一個獨一無二的回調函數名,用什麼呢,除了時間還有跟好的解決方法嗎?jsc就是當前時間 ,jsc =now(); 然後用此回調函數名換掉?,這樣就符合了參數字符串的格式。
window[jsonp],為回調函數注冊。
if ( s.dataType === "script" && s.cache === null ) { s.cache = false; }   if ( s.cache === false && type === "GET" ) { var ts = now();   // try replacing _= if it is there var ret = s.url.replace(rts, "$1_=" + ts + "$2");   // if nothing was replaced, add timestamp to the end s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); }   // If data is available, append data to url for get requests if ( s.data && type === "GET" ) { s.url += (rquery.test(s.url) ? "&" : "?") + s.data; }   // Watch for a new set of requests if ( s.global && ! jQuery.active++ ) { jQuery.event.trigger( "ajaxStart" ); }   // Matches an absolute URL, and saves the domain var parts = rurl.exec( s.url ), remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);   // If we're requesting a remote document // and trying to load JSON or Script with a GET if ( s.dataType === "script" && type === "GET" && remote ) { var head = document.getElementsByTagName("head")[0] || document.documentElement; var script = document.createElement("script"); script.src = s.url; if ( s.scriptCharset ) { script.charset = s.scriptCharset; }   // Handle Script loading if ( !jsonp ) { var done = false;   // Attach handlers for all browsers script.onload = script.onreadystatechange = function() { if ( !done && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") ) { done = true; success(); complete();   // Handle memory leak in IE script.onload = script.onreadystatechange = null; if ( head && script.parentNode ) { head.removeChild( script ); } } }; }   // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709 and #4378). head.insertBefore( script, head.firstChild );   // We handle everything using the script element injection return undefined; }
進行ajax進行請求是可能會會有緩存文件(當然著僅存在請求方式為Get時,關於Get和post在ajax請求時的具體區別請參考w3school中的介紹),當請求格式為‘script’(可能是後來轉換的如jsonp)時,則沒有緩存文件,實現方式是為每一次請求加一個時間戳,ts也是now,同jsc一樣。
如果請求方式為Get,將請求參數加到url後面。
下面介紹一下ajax中自定義事件 這是官方的ajax事件列表(自己後面有對應的翻譯,水品有限)
ajaxStart (Global Event)
This event is broadcast if an Ajax request is started and no other Ajax requests are currently running.
(此事件只有當當前有一個ajax請求已開始且當前沒有其他ajax請求正在運行時觸發)
beforeSend (Local Event)
This event, which is triggered before an Ajax request is started, allows you to modify the XMLHttpRequest object (setting additional headers, if need be.)
(此事件在一個ajax請求開始前觸發。讓你可以修改請求對象(如設置其他頭))
ajaxSend (Global Event)
This global event is also triggered before the request is run.
(此事件在在請求運行前觸發)
success (Local Event)
This event is only called if the request was successful (no errors from the server, no errors with the data).
(此事件只有放請求成功時觸發(沒有從服務器返回任何錯誤,返回數據沒有任何錯誤))
ajaxSuccess (Global Event)
This event is also only called if the request was successful.
(此事件在請求成功時觸發)
error (Local Event)
This event is only called if an error occurred with the request (you can never have both an error and a success callback with a request).
當請求錯誤時觸發(一個請求不可能同時觸發error和success兩個回調函數)
ajaxError (Global Event)
This global event behaves the same as the local error event.
同上
complete (Local Event)
This event is called regardless of if the request was successful, or not. You will always receive a complete callback, even for synchronous requests.
無論請求成功與否都會觸發
ajaxComplete (Global Event)
This event behaves the same as the complete event and will be triggered every time an Ajax request finishes.
同上
ajaxStop (Global Event)
This global event is triggered if there are no more Ajax requests being processed.
當前沒有請求處理是觸發
上面都列出了這些事件的觸發條件。當符合這些條件是就要trigger這些事件,至於有沒有注冊處理函數那就是用戶的事情。
參照上述的這些觸發條件,可以很了解ajax函數中對這些事件的觸發。要說的是jquery.active的初始值是0;
rurl = /^(\w+:)?\/\/([^\/?#]+)/ 主要又來將url的歇息名稱 和主機名取出來。當當前請求是資源的協議名或主機名與與浏覽器當前的對象內容不同,則為遠程跨域訪問,設置remote為true。
如果是請求遠程資源且為Get,請求類型為script,則創建script的dom對象,並設置相關屬性,已加載script腳本。最後設置script加載成功時的回調函數。
最後返回undefine,是處理script加載的最後步驟。如果加載的是script則到此處請求結束。
這裡主要是處理了script和jsonp著兩種數據類型的請求。
var requestDone = false;   // Create the request object var xhr = s.xhr();   if ( !xhr ) { return; }   // Open the socket // Passing null username, generates a login popup on Opera (#2865) if ( s.username ) { xhr.open(type, s.url, s.async, s.username, s.password); } else { xhr.open(type, s.url, s.async); }   // Need an extra try/catch for cross domain requests in Firefox 3 try { // Set the correct header, if data is being sent if ( s.data || origSettings && origSettings.contentType ) { xhr.setRequestHeader("Content-Type", s.contentType); }   // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( jQuery.lastModified[s.url] ) { xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]); }   if ( jQuery.etag[s.url] ) { xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]); } }   // Set header so the called script knows that it's an XMLHttpRequest // Only send the header if it's not a remote XHR if ( !remote ) { xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); }   // Set the Accepts header for the server, depending on the dataType xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? s.accepts[ s.dataType ] + ", */*" : s.accepts._default ); } catch(e) {}   // Allow custom headers/mimetypes and early abort if ( s.beforeSend && s.beforeSend.call(callbackContext, xhr, s) === false ) { // Handle the global AJAX counter if ( s.global && ! --jQuery.active ) { jQuery.event.trigger( "ajaxStop" ); }   // close opended socket xhr.abort(); return false; }   if ( s.global ) { trigger("ajaxSend", [xhr, s]); }
這一段代碼主要是獲得xhr,並在xhrsend之前設置一些header,並調用一些回調函數,比如beforeSend,CallBackcontext是我們在originSettings設置的回調函數的執行上下文。從判斷條件可以看出,我們在beforesennd中返回false就會導致此次請求取消。如果當前沒有其他請求的話還會觸發ajaxstop事件。
var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) { // The request was aborted if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) { // Opera doesn't call onreadystatechange before this point // so we simulate the call if ( !requestDone ) { complete(); }   requestDone = true; if ( xhr ) { xhr.onreadystatechange = jQuery.noop; }   // The transfer is complete and the data is available, or the request timed out } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) { requestDone = true; xhr.onreadystatechange = jQuery.noop;   status = isTimeout === "timeout" ? "timeout" : !jQuery.httpSuccess( xhr ) ? "error" : s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" : "success";   var errMsg;   if ( status === "success" ) { // Watch for, and catch, XML document parse errors try { // process the data (runs the xml through httpData regardless of callback) data = jQuery.httpData( xhr, s.dataType, s ); } catch(err) { status = "parsererror"; errMsg = err; } }   // Make sure that the request was successful or notmodified if ( status === "success" || status === "notmodified" ) { // JSONP handles its own success callback if ( !jsonp ) { success(); } } else { jQuery.handleError(s, xhr, status, errMsg); }   // Fire the complete handlers complete();   if ( isTimeout === "timeout" ) { xhr.abort(); }   // Stop memory leaks if ( s.async ) { xhr = null; } } };
本人覺得onreadystatechange是ajax函數另一個比較重要的地方。
最關心也是當status==‘success’時的處理。下面是jquery工具函數httpData的具體內容
httpData: function( xhr, type, s ) { var ct = xhr.getResponseHeader("content-type") || "", xml = type === "xml" || !type && ct.indexOf("xml") >= 0, data = xml ? xhr.responseXML : xhr.responseText;   if ( xml && data.documentElement.nodeName === "parsererror" ) { jQuery.error( "parsererror" ); }   // Allow a pre-filtering function to sanitize the response // s is checked to keep backwards compatibility if ( s && s.dataFilter ) { data = s.dataFilter( data, type ); }   // The filter can actually parse the response if ( typeof data === "string" ) { // Get the JavaScript object, if JSON is used. if ( type === "json" || !type && ct.indexOf("json") >= 0 ) { data = jQuery.parseJSON( data );   // If the type is "script", eval it in global context } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) { jQuery.globalEval( data ); } }   return data; },
ajax返回的內容只有兩種格式,responseText 和responseXML。如果我們設置了datafilter,那麼此函數是會在所有對數據的操作之前就行過濾。我們請求格式中json,jsonp,script都是回憶string返回數據。如果是jsonp或是script則先執行data對應的js代碼,然後返回數據。(就是客戶端腳本注入),如果是json類型,則會先將字符串解析為js對象,然後返回。
// Override the abort handler, if we can (IE doesn't allow it, but that's OK) // Opera doesn't fire onreadystatechange at all on abort try { var oldAbort = xhr.abort; xhr.abort = function() { if ( xhr ) { oldAbort.call( xhr ); }   onreadystatechange( "abort" ); }; } catch(e) { }   // Timeout checker if ( s.async && s.timeout > 0 ) { setTimeout(function() { // Check to see if the request is still happening if ( xhr && !requestDone ) { onreadystatechange( "timeout" ); } }, s.timeout); }   // Send the data try { xhr.send( type === "POST" || type === "PUT" || type === "DELETE" ? s.data : null ); } catch(e) { jQuery.handleError(s, xhr, null, e); // Fire the complete handlers complete(); }   // firefox 1.5 doesn't fire statechange for sync requests if ( !s.async ) { onreadystatechange(); }   function success() { // If a local callback was specified, fire it and pass it the data if ( s.success ) { s.success.call( callbackContext, data, status, xhr ); }   // Fire the global callback if ( s.global ) { trigger( "ajaxSuccess", [xhr, s] ); } }   function complete() { // Process result if ( s.complete ) { s.complete.call( callbackContext, xhr, status); }   // The request was completed if ( s.global ) { trigger( "ajaxComplete", [xhr, s] ); }   // Handle the global AJAX counter if ( s.global && ! --jQuery.active ) { jQuery.event.trigger( "ajaxStop" ); } } function trigger(type, args) { (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args); }   // return XMLHttpRequest to allow aborting the request etc. return xhr;
下面就是一些細節上處理,事件的觸發,浏覽器的兼容處理,當然還有最重要的一句  xhr.send( type === "POST" || type === "PUT" || type === "DELETE" ? s.data : null );
最後函數會返回此xhr請求對象。此外函數success conplete看來應該是我們自己寫的回調函數會取代默認,其實success 和compete中還有ajax事件出發的任務,所以我們在settings中寫的回調函數只是被ajax的相應函數調用而已。
最後一點,在上面我們講jsonp請求時為其注冊過一個回調函數,你可能會問這到底什麼時候調用呢,其實這就要看服務器端相應回來的script中有沒有對此函數的調用了。因為我們已經將回調函數名傳遞給了服務器端了。(這是我的理解,關於jsonp我也是知之甚少)
雖然原本ajax請求結果只有兩種responseText和responseXML但是ajax在其上根據具體的協議為我們作了很多處理,雖然我們看不到,但還是應該有所了解。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved