JQuery3.1.1原始碼解讀(十九)【ajax】
關於 ajax,東西太多了,我本來想避開 ajax,避而不提,但覺得 ajax 這麼多內容,不說實在可惜。
寫在 jQuery 的 ajax 之前
首先,我們還是來了解一下 js 中的 http 請求。http 協議中有請求體和響應體,對於請求的一方,無論是哪一種語言,我比較關心如下幾個方面:請求的配置引數包括 url,post/get 等;請求有請求頭,那麼請求頭的引數又該由哪個函式來設定;如何判斷請求已經成功;響應狀態碼和響應資料該如何獲得等等。
XMLHttpRequest 物件
每天都喊著要寫原生的 js 請求,那麼來了,就是這個函式 XMLHttpRequest
,它是一套可以在Javascript、VbScript、Jscript等指令碼語言中通過http協議傳送或接收XML及其他資料的一套API,萬惡的低版本 IE 有個相容的 ActiveXObject
它有兩個版本,第一個版本的功能很少,在不久之後又有了點一個更完善的版本 2.0,功能更全。如果你感興趣,可以來這裡看一下XMLHttpRequest。如果你對 http 協議有著很好的掌握的話,也可以看下面的內容。
實現一個簡單的 ajax 請求
如果你碰到面試官,讓你手寫一個原生的 ajax 請求,那麼下面的東西可能對你非常有幫助:
// myAjax
var myAjax = (function(){
var defaultOption = {
url: false,
type: 'GET',
data: null,
success: false ,
complete: false
}
var ajax = function(options){
for(var i in defaultOption){
options[i] = options[i] || defaultOption[i];
}
// http 物件
var xhr = new XMLHttpRequest();
var url = options.url;
xhr.open(options.type, url);
// 監聽
xhr.onreadystatechange = function (){
if(xhr.readyState == 4){
var result, status = xhr.status;
}
if(status >= 200 && status < 300 || status == 304){
result = xhr.responseText;
if(window.JSON){
result = JSON.parse(result);
}else{
result = eval('(' + result + ')');
}
ajaxSuccess(result)
}
}
// post
if(options.type.toLowerCase() === 'post'){
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencode');
}
xhr.send(options.data);
function ajaxSuccess(data){
var status = 'success';
options.success && options.success(data, options, status, xhr);
options.complete && options.complete(status);
}
}
// 閉包返回
return ajax;
})()
測試在下面:
var success = function(data){
console.log(data['blog'])
}
var complete = function(status){
if(status == 'success'){
console.log('success')
}else{
console.log('failed')
}
}
myAjax( {
url: 'https://api.github.com/users/songjinzhong',
success: success,
complete: complete
} );
可以得到 XMLHttpRequest 的簡單用法:
- 通過 new XMLHttpRequest() 建立一個 http 請求物件;
- open 函式的作用是設定要開啟的 url 和型別,建立一個連線,但此時請求並沒有傳送;
- setRequestHeader 來設定請求頭資訊;
- send 函式像伺服器傳送資料請求;
- onreadystatechange 是一個監聽函式,當 readyState 改變的時候執行,1-2-3-4,4 表示成功返回。xhr.responseText 是返回的響應資料,很明顯,這裡是 json 格式,實際要通過響應頭來判斷,這裡省去了這一步,getAllResponseHeaders 可以獲得所有響應頭;
- success 函式和 complete 函式執行的位置和順序問題。
jQuery ajax 的特點
通過上面的例子,應該可以對 js 的 http 請求有個大致的瞭解,而 jQuery 的處理則複雜的多,也涉及到和上面功能類似的一些函式,而對於 callback 和 deferred,jQuery 本身就支援:
var deferred = jQuery.Deferred(),
completeDeferred = jQuery.Callbacks( "once memory" );
所以說 jQuery 是一個自給自足的庫,一點也不過分,前面有 Sizzle,整個原始碼到處都充滿著 extend 函式,等等。
jQuery.ajaxSetup
ajaxSetup 是在 ajax 函式裡比較早執行的一個函式,這個函式主要是用來校準引數用的;
jQuery.extend( {
ajaxSetup: function( target, settings ) {
return settings ?
// 雙層的 ajaxExtend 函式
ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
// Extending ajaxSettings
ajaxExtend( jQuery.ajaxSettings, target );
},
} );
ajaxSettings 是一個物件,具體是幹什麼用的,看看就知道了:
jQuery.ajaxSettings = {
url: location.href,
type: "GET",
isLocal: rlocalProtocol.test( location.protocol ),
global: true,
processData: true,
async: true,
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
accepts: {
"*": allTypes,
text: "text/plain",
html: "text/html",
xml: "application/xml, text/xml",
json: "application/json, text/javascript"
},
contents: {
xml: /\bxml\b/,
html: /\bhtml/,
json: /\bjson\b/
},
responseFields: {
xml: "responseXML",
text: "responseText",
json: "responseJSON"
},
// Data converters
// Keys separate source (or catchall "*") and destination types with a single space
converters: {
// Convert anything to text
"* text": String,
// Text to html (true = no transformation)
"text html": true,
// Evaluate text as a json expression
"text json": JSON.parse,
// Parse text as xml
"text xml": jQuery.parseXML
},
// For options that shouldn't be deep extended:
// you can add your own custom options here if
// and when you create one that shouldn't be
// deep extended (see ajaxExtend)
flatOptions: {
url: true,
context: true
}
}
ajaxSettings 原來是一個加強版的 options。
ajaxExtend 是用來將 ajax 函式引數進行標準化的,看看哪些引數沒有賦值,讓它等於預設值,由於 ajaxExtend 是雙層的,具體要除錯了才能更明白。
function ajaxExtend( target, src ) {
var key, deep,
flatOptions = jQuery.ajaxSettings.flatOptions || {};
for ( key in src ) {
if ( src[ key ] !== undefined ) {
( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
}
}
if ( deep ) {
jQuery.extend( true, target, deep );
}
return target;
}
ajax.jqXHR
在 ajax 中有一個非常重要的物件,jqXHR,它雖然是一個簡稱,但通過縮寫也大致能猜出它是 jquery-XMLHttpRequest
。
jqXHR = {
readyState: 0, // 0-4
// 熟悉響應頭的對這個應該不陌生,將響應頭資料按照 key value 儲存起來
getResponseHeader: function( key ) {
var match;
if ( completed ) {
if ( !responseHeaders ) {
responseHeaders = {};
while ( ( match = /^(.*?):[ \t]*([^\r\n]*)$/mg.exec( responseHeadersString ) ) ) {
responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
}
}
match = responseHeaders[ key.toLowerCase() ];
}
return match == null ? null : match;
},
// Raw string
getAllResponseHeaders: function() {
return completed ? responseHeadersString : null;
},
// 手動設定請求頭
setRequestHeader: function( name, value ) {
if ( completed == null ) {
name = requestHeadersNames[ name.toLowerCase() ] =
requestHeadersNames[ name.toLowerCase() ] || name;
requestHeaders[ name ] = value;
}
return this;
},
// Overrides response content-type header
overrideMimeType: function( type ) {
if ( completed == null ) {
s.mimeType = type;
}
return this;
},
// Status-dependent callbacks
statusCode: function( map ) {
var code;
if ( map ) {
if ( completed ) {
// Execute the appropriate callbacks
jqXHR.always( map[ jqXHR.status ] );
} else {
// Lazy-add the new callbacks in a way that preserves old ones
for ( code in map ) {
statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
}
}
}
return this;
},
// Cancel the request
abort: function( statusText ) {
var finalText = statusText || strAbort;
if ( transport ) {
transport.abort( finalText );
}
done( 0, finalText );
return this;
}
};
jqXHR 已經完全可以取代 XHR 物件了,函式都進行擴充套件了。
ajaxTransport
那麼 XMLHttpRequest 這個函式到底在哪呢?
jQuery 中有兩個屬性,分別是 ajaxPrefilter
和 ajaxTransport
,它們是由 addToPrefiltersOrTransports
函式構造的。主要來看 ajaxTransport 函式:
jQuery.ajaxTransport( function( options ) {
var callback, errorCallback;
// Cross domain only allowed if supported through XMLHttpRequest
if ( support.cors || xhrSupported && !options.crossDomain ) {
return {
send: function( headers, complete ) {
var i,
xhr = options.xhr();// xhr() = XMLHttpRequest()
xhr.open(
options.type,
options.url,
options.async,
options.username,
options.password
);
// Apply custom fields if provided
if ( options.xhrFields ) {
for ( i in options.xhrFields ) {
xhr[ i ] = options.xhrFields[ i ];
}
}
// Override mime type if needed
if ( options.mimeType && xhr.overrideMimeType ) {
xhr.overrideMimeType( options.mimeType );
}
// X-Requested-With header
// For cross-domain requests, seeing as conditions for a preflight are
// akin to a jigsaw puzzle, we simply never set it to be sure.
// (it can always be set on a per-request basis or even using ajaxSetup)
// For same-domain requests, won't change header if already provided.
if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
headers[ "X-Requested-With" ] = "XMLHttpRequest";
}
// Set headers
for ( i in headers ) {
xhr.setRequestHeader( i, headers[ i ] );
}
// Callback
callback = function( type ) {
return function() {
if ( callback ) {
callback = errorCallback = xhr.onload =
xhr.onerror = xhr.onabort = xhr.onreadystatechange = null;
if ( type === "abort" ) {
xhr.abort();
} else if ( type === "error" ) {
// Support: IE <=9 only
// On a manual native abort, IE9 throws
// errors on any property access that is not readyState
if ( typeof xhr.status !== "number" ) {
complete( 0, "error" );
} else {
complete(
// File: protocol always yields status 0; see #8605, #14207
xhr.status,
xhr.statusText
);
}
} else {
complete(
xhrSuccessStatus[ xhr.status ] || xhr.status,
xhr.statusText,
// Support: IE <=9 only
// IE9 has no XHR2 but throws on binary (trac-11426)
// For XHR2 non-text, let the caller handle it (gh-2498)
( xhr.responseType || "text" ) !== "text" ||
typeof xhr.responseText !== "string" ?
{ binary: xhr.response } :
{ text: xhr.responseText },
xhr.getAllResponseHeaders()
);
}
}
};
};
// Listen to events
xhr.onload = callback();
errorCallback = xhr.onerror = callback( "error" );
// Support: IE 9 only
// Use onreadystatechange to replace onabort
// to handle uncaught aborts
if ( xhr.onabort !== undefined ) {
xhr.onabort = errorCallback;
} else {
xhr.onreadystatechange = function() {
// Check readyState before timeout as it changes
if ( xhr.readyState === 4 ) {
// Allow onerror to be called first,
// but that will not handle a native abort
// Also, save errorCallback to a variable
// as xhr.onerror cannot be accessed
window.setTimeout( function() {
if ( callback ) {
errorCallback();
}
} );
}
};
}
// Create the abort callback
callback = callback( "abort" );
try {
// Do send the request (this may raise an exception)
xhr.send( options.hasContent && options.data || null );
} catch ( e ) {
// #14683: Only rethrow if this hasn't been notified as an error yet
if ( callback ) {
throw e;
}
}
},
abort: function() {
if ( callback ) {
callback();
}
}
};
}
} );
ajaxTransport 函式返回值有兩個,其中 send 就是傳送函數了,一步一步,傳送下來,無需多說明。
另外,ajax 對於 jQuery 物件在 ajax 過程提供了很多回調函式:
jQuery.each( [
"ajaxStart",
"ajaxStop",
"ajaxComplete",
"ajaxError",
"ajaxSuccess",
"ajaxSend"
], function( i, type ) {
jQuery.fn[ type ] = function( fn ) {
return this.on( type, fn );
};
} );
jQuery.event.trigger( "ajaxStart" );
...
globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
...
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",[ jqXHR, s, isSuccess ? success : error ] );
...
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
...
jQuery.event.trigger( "ajaxStop" );
ajax 東西太多了,至少有 1000 行的程式碼吧。
總結
關於 ajax,不想去深入研究了,最近暑假實習校招已經開始啟動了,暫時先放一放吧,以後有時間再來填坑吧。