由于jQuery ajax對Callbacks、Deferred、serialize、event等模塊的依賴,建議對這些模塊沒有認識的朋友看一下 jQuery Callbacks 、 jQuery Deferred 、 jQuery serialize 、 jQuery event(上) 、 jQuery event(下) 。
這篇文章主要分析的是擁有380+行的jQuery.ajax函數,該函數是jQuery ajax的核心函數,jQuery的其他ajax方法幾乎都是基于該方法的。
上一篇文章我們了解了Baidu ajax(當然是舊版的,還是被簡化的……),那么我們想給這個簡單的ajax方法添加什么功能呢?
?
可鏈式操作
既然是jQuery必然先要解決的是鏈式操作的問題。
jQuery中的Deferred可以實現異步鏈式模型實現,Promise對象可以輕易的綁定成功、失敗、進行中三種狀態的回調函數,然后通過在狀態碼在來回調不同的函數就行了。
?
但僅僅返回一個promise沒什么用
promise只能處理異步,我們需要返回一個更有用的東西。
jqXHR就是這樣的東西,實際上他是一個山寨版的XHR對象。
這樣我們就可以擴展一下XHR對象的功能, 提供一定的容錯處理,暴露給外部提供一些方法。
//
贗品xhr,或者說山寨xhr……╮(╯▽╰)╭
//
為了能夠實現鏈式操作
//
順便擴展一下xhr對象功能
//
還有提供一定的容錯處理
jqXHR =
{
//
準備狀態
readyState: 0
,
//
如果需要,創建一個響應頭參數的表
getResponseHeader:
function
( key ) {
var
match;
//
如果狀態為2,狀態2表示ajax完成
if
( state === 2
) {
//
如果沒有相應頭
if
( !
responseHeaders ) {
//
相應頭設空
responseHeaders =
{};
while
( (match =
rheaders.exec( responseHeadersString )) ) {
//
組裝相應頭
responseHeaders[ match[1].toLowerCase() ] = match[ 2
];
}
}
//
響應頭對應的key的值
match =
responseHeaders[ key.toLowerCase() ];
}
//
返回
return
match ==
null
?
null
: match;
},
//
返回響應頭字符串
getAllResponseHeaders:
function
() {
//
看看是否接收到了,接收到直接返回,否則為null
return
state === 2 ? responseHeadersString :
null
;
},
//
設置請求頭
setRequestHeader:
function
( name, value ) {
var
lname =
name.toLowerCase();
//
如果state不為0
if
( !
state ) {
//
如果requestHeadersNames[ lname ]不為空,
//
則requestHeadersNames[ lname ]不變,name設置為該值
//
否則,requestHeadersNames[ lname ]不空,
//
則requestHeadersNames[ lname ]設置為name,
//
該映射關系用于避免用戶大小寫書寫錯誤之類的問題,容錯處理
name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] ||
name;
//
現在的name是對的,或者是第一次設置這個name,不需要容錯
//
設置請求頭對應值
requestHeaders[ name ] =
value;
}
return
this
;
},
//
重寫相應頭content-type
overrideMimeType:
function
( type ) {
if
( !
state ) {
s.mimeType
=
type;
}
return
this
;
},
//
對應狀態的回調函數集
statusCode:
function
( map ) {
var
code;
//
如果map存在,準備組裝
if
( map ) {
//
如果狀態小于2,表示舊的回調可能還沒有用到
if
( state < 2
) {
//
遍歷map里面的所有code
for
( code
in
map ) {
//
用類似鏈表的方式添加,以保證舊的回調依然存在
statusCode[ code ] =
[ statusCode[ code ], map[ code ] ];
}
//
狀態大于2,證明已經完成了
}
else
{
//
無論Deferred成功還是失敗都執行當前狀態回調
jqXHR.always( map[ jqXHR.status ] );
}
}
return
this
;
},
//
中斷請求
abort:
function
( statusText ) {
var
finalText = statusText ||
strAbort;
//
可以先理解成XHR對象,當然這也不是真正的XHR對象
if
( transport ) {
transport.abort( finalText );
}
//
調用done,表示干完了
done( 0
, finalText );
return
this
;
}
};
?
可是這還沒有鏈式啊!
怎么把jqXHR變成一個Promise呢?
讓一個對象擁有另一個對象的方法,大家會想到什么呢?
繼承?
不,直接插上去就好了……這里就體現了Javascript“易插拔”的特點……自己亂起的……囧rz……
應該說動態、弱類的特點。
什么意思?就是將Promise上的方法插到jqXHR對象上就行了。
//
在jqXHR粘上promise的所有方法,此時jqXHR就很像一個promise了
//
實際上就是用jQuery.extend(jqXHR, promise)而已
//
jqXHR剛山寨了xhr對象,又開始山寨promise對象了
//
順便把jqXHR的complete綁上completeDeferred.add
//
意思是jqXHR狀態完成后調用completeDeferred這個Callbacks列隊
//
話說promise里面并沒有complete這個方法
//
后面我們可以看到,作者大人又要給jqXHR插上complete、success、error方法
//
Javascript就是這樣簡單,即插即用……囧rz
deferred.promise( jqXHR ).complete =
completeDeferred.add;
//
綁定jqXHR.success為promise里面的done方法
jqXHR.success =
jqXHR.done;
//
綁定jqXHR.error為promise里面的fail方法
jqXHR.error = jqXHR.fail;
這樣子直接插上去在強類語言中是做不到的,當然也可以用組合模式來實現一個對象擁有多個對象的方法,但是卻無法動態的去給對象添加各種方法。
強類語言會限制對象一輩子只能“會”那么幾種“方法”,Javascript的對象可以在生命周期內隨意“學習”各種“方法”。
所以說,強類語言和Javascript的對象模型是有差別的,將設計模式生搬硬套進Javascript或許是錯誤的。
我們再舉個例子,比如如果用所謂的Javascript組合模式來重寫上面的代碼可能會是這樣的:
// 定義局部變量deferred和completeDeferred function JQXHR(){ // 定義jqXHR的屬性和方法 for (i in deferred.promise){ this [i] = this .promise[i]; } this .complete = completeDeferred.add; this .success = this .done; this .error = this .done } var jqXHR = new JQXHR();大家覺得哪個好呢?
變成上面的樣子主要是因為要解決下面兩個問題:
- jqXHR由于是暴露在外的,他不能包含deferred和completeDeferred,不允許用戶在外部操作這兩個對象的狀態。
- deferred和completeDeferred也不能成為jqXHR的私有變量,因為還要更具ajax事件觸發。
?
提供全局事件,使得UI可以根據ajax狀態進行改變
這里主要利用了jQuery.event.trigger和jQuery.fn.trigger模擬發事件。
//
如果需要,而且全局事件沒有被觸發過
if
( fireGlobals && jQuery.active++ === 0
) {
//
則通過jQuery.event.trigger模擬觸發
jQuery.event.trigger("ajaxStart"
);
}
當ajax開始時模擬全局事件,ajaxStart。
//
如果需要,對特定對象觸發全局事件ajaxSend
if
( fireGlobals ) {
globalEventContext.trigger(
"ajaxSend"
, [ jqXHR, s ] );
}
ajax發送消息,觸發ajaxSend。
//
如果需要觸發全局事件
if
( fireGlobals ) {
//
對指定元素觸發事件ajaxSuccess或者ajaxError
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError"
,
[ jqXHR, s, isSuccess
?
success : error ] );
}
//
回調完成后的Callbacks隊列
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
//
如果需要觸發全局事件
if
( fireGlobals ) {
//
對指定元素觸發事件ajaxComplete
globalEventContext.trigger( "ajaxComplete"
, [ jqXHR, s ] );
//
該ajax觸發完畢,標記active減1,如果為0,證明所有ajax結束
if
( !( --
jQuery.active ) ) {
//
觸發ajaxStop事件
jQuery.event.trigger("ajaxStop"
);
}
}
結束時候觸發ajaxSuccess或ajaxError,再出發ajaxComplete,如果全部ajax結束則觸發ajaxStop。
?
?
是否允許使用緩存數據
//
如果不需要content
//
看看需不需要加其他信息
if
( !
s.hasContent ) {
//
如果data存在,那么已經序列化
if
( s.data ) {
//
添加到cacheURL中
cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) +
s.data );
//
刪除掉則后面就不會被發送出去了
delete
s.data;
}
//
看看是否需要避免數據從緩存中讀取
if
( s.cache ===
false
) {
s.url
= rts.test( cacheURL ) ?
//
如果已經有_參數,那么設置他的值
cacheURL.replace( rts, "$1_=" + ajax_nonce++
) :
//
否則添加一個_ = xxx在URL后面
cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++
;
}
}
通過給地址附加參數_=xxx來避免緩存。
//
看看需不需要設置If-Modified-Since和If-None-Match頭信息
if
( s.ifModified ) {
if
( jQuery.lastModified[ cacheURL ] ) {
jqXHR.setRequestHeader(
"If-Modified-Since"
, jQuery.lastModified[ cacheURL ] );
}
if
( jQuery.etag[ cacheURL ] ) {
jqXHR.setRequestHeader(
"If-None-Match"
, jQuery.etag[ cacheURL ] );
}
}
以及設置If-Modified-Since和If-None-Match頭信息。
//
看看是否需要緩存置If-Modified-Since和If-None-Match頭
if
( s.ifModified ) {
//
取得Last-Modified
modified = jqXHR.getResponseHeader("Last-Modified"
);
//
如果Last-Modified存在
if
( modified ) {
//
在jQuery.lastModified[cacheURL]保存Last-Modified
jQuery.lastModified[ cacheURL ] =
modified;
}
//
取得etag
modified = jqXHR.getResponseHeader("etag"
);
//
如果etag存在
if
( modified ) {
//
在jQuery.etag[cacheURL]緩存etag
jQuery.etag[ cacheURL ] =
modified;
}
}
緩存 If-Modified-Since和If-None-Match頭信息。
?
設置超時
//
如果是異步,并且設置了超時
if
( s.async && s.timeout > 0
) {
//
設置超時
timeoutTimer = setTimeout(
function
() {
jqXHR.abort(
"timeout"
);
}, s.timeout );
}
主要功能分析完了,完整備注代碼見下。?
?
完整備注
jQuery.ajax =
function
( url, options ) {
//
如果url是一個obj,模擬1.5版本以前的方法
if
(
typeof
url === "object"
) {
options
=
url;
url
=
undefined;
}
//
設置options
options = options ||
{};
var
transport,
//
緩存cacheURL
cacheURL,
//
響應頭
responseHeadersString,
responseHeaders,
//
超時控制器
timeoutTimer,
//
跨域判定變量
parts,
//
是否需要觸發全局事件
fireGlobals,
//
循環變量
i,
//
通過jQuery.ajaxSetup改造參數對象
s =
jQuery.ajaxSetup( {}, options ),
//
回調指定上下文,也就是他的this
callbackContext = s.context ||
s,
//
全局事件中的相應函數的指定上下文
//
有s.context,且是DOM節點,或者jQuery收集器
globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
//
通過jQuery包裝
jQuery( callbackContext ) :
//
否則為jQuery.event
jQuery.event,
//
新建一個deferred
deferred =
jQuery.Deferred(),
//
deferred完成后的Callbacks隊列
completeDeferred = jQuery.Callbacks("once memory"
),
//
對應狀態的回調函數集
statusCode = s.statusCode ||
{},
//
請求頭
requestHeaders =
{},
requestHeadersNames
=
{},
//
包裝類jqXHR的狀態
state = 0
,
//
默認中斷消息
strAbort = "canceled"
,
//
贗品xhr,或者說山寨xhr……╮(╯▽╰)╭
//
為了能夠實現鏈式操作
//
順便擴展一下xhr對象功能
//
還有提供一定的容錯處理
jqXHR =
{
//
準備狀態
readyState: 0
,
//
如果需要,創建一個響應頭參數的表
getResponseHeader:
function
( key ) {
var
match;
//
如果狀態為2,狀態2表示ajax完成
if
( state === 2
) {
//
如果沒有相應頭
if
( !
responseHeaders ) {
//
相應頭設空
responseHeaders =
{};
while
( (match =
rheaders.exec( responseHeadersString )) ) {
//
組裝相應頭
responseHeaders[ match[1].toLowerCase() ] = match[ 2
];
}
}
//
響應頭對應的key的值
match =
responseHeaders[ key.toLowerCase() ];
}
//
返回
return
match ==
null
?
null
: match;
},
//
返回響應頭字符串
getAllResponseHeaders:
function
() {
//
看看是否接收到了,接收到直接返回,否則為null
return
state === 2 ? responseHeadersString :
null
;
},
//
緩存請求頭
setRequestHeader:
function
( name, value ) {
var
lname =
name.toLowerCase();
//
如果state不為0
if
( !
state ) {
//
如果requestHeadersNames[ lname ]不為空,
//
則requestHeadersNames[ lname ]不變,name設置為該值
//
否則,requestHeadersNames[ lname ]不空,
//
則requestHeadersNames[ lname ]設置為name,
//
該映射關系用于避免用戶大小寫書寫錯誤之類的問題,容錯處理
name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] ||
name;
//
現在的name是對的,或者是第一次設置這個name,不需要容錯
//
設置請求頭對應值
requestHeaders[ name ] =
value;
}
return
this
;
},
//
重寫相應頭content-type
overrideMimeType:
function
( type ) {
if
( !
state ) {
s.mimeType
=
type;
}
return
this
;
},
//
對應狀態的回調函數集
statusCode:
function
( map ) {
var
code;
//
如果map存在,準備組裝
if
( map ) {
//
如果狀態小于2,表示舊的回調可能還沒有用到
if
( state < 2
) {
//
遍歷map里面的所有code
for
( code
in
map ) {
//
用類似鏈表的方式添加,以保證舊的回調依然存在
statusCode[ code ] =
[ statusCode[ code ], map[ code ] ];
}
//
狀態大于2,證明已經完成了
}
else
{
//
無論Deferred成功還是失敗都執行當前狀態回調
jqXHR.always( map[ jqXHR.status ] );
}
}
return
this
;
},
//
中斷請求
abort:
function
( statusText ) {
var
finalText = statusText ||
strAbort;
//
可以先理解成XHR對象,當然這也不是真正的XHR對象
if
( transport ) {
transport.abort( finalText );
}
//
調用done,表示干完了
done( 0
, finalText );
return
this
;
}
};
//
在jqXHR粘上promise的所有方法,此時jqXHR就很像一個promise了
//
實際上就是用jQuery.extend(jqXHR, promise)而已
//
jqXHR剛山寨了xhr對象,又開始山寨promise對象了
//
順便把jqXHR的complete綁上completeDeferred.add
//
意思是jqXHR狀態完成后調用completeDeferred這個Callbacks列隊
//
話說promise里面并沒有complete這個方法
//
后面我們可以看到,作者大人又要給jqXHR插上complete、success、error方法
//
Javascript就是這樣簡單,即插即用……囧rz
deferred.promise( jqXHR ).complete =
completeDeferred.add;
//
綁定jqXHR.success為promise里面的done方法
jqXHR.success =
jqXHR.done;
//
綁定jqXHR.error為promise里面的fail方法
jqXHR.error =
jqXHR.fail;
//
確定url參數,否則用當前地址。將地址的#號后面的所有東西去掉
//
比如http://127.0.0.1#main,去掉這個#main
//
如果開頭是//,及數據傳輸協議沒有,那么用當前頁面的數據傳輸協議替換
//
比如//127.0.0.1,變成http://127.0.0.1
s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "http://"
);
//
定義type,向后兼容
s.type = options.method || options.type || s.method ||
s.type;
//
取出數據類型列表
//
沒有則為"*",
//
有則認為是用空格分隔的字符串,將其變成數組
s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""
];
//
當協議、主機、端口和當前不匹配時,證明這是一個跨域請求
if
( s.crossDomain ==
null
) {
//
分隔當前url
parts =
rurl.exec( s.url.toLowerCase() );
//
判定是否是跨域
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已經是一個字符串了,那么就不用轉換了
if
( s.data && s.processData &&
typeof
s.data !== "string"
) {
//
序列化
s.data =
jQuery.param( s.data, s.traditional );
}
//
預過濾
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
//
如果請求被prefilter終止,則退出
if
( state === 2
) {
return
jqXHR;
}
//
看看需不需要觸發全局事件
fireGlobals =
s.global;
//
如果需要,而且全局事件沒有被觸發過
if
( fireGlobals && jQuery.active++ === 0
) {
//
則通過jQuery.event.trigger模擬觸發
jQuery.event.trigger("ajaxStart"
);
}
//
將類型大寫
s.type =
s.type.toUpperCase();
//
判斷需不需要設置content
s.hasContent = !
rnoContent.test( s.type );
//
緩存URL,用來在之后設置If-Modified-Since和If-None-Match
cacheURL =
s.url;
//
如果不需要content
//
看看需不需要加其他信息
if
( !
s.hasContent ) {
//
如果data存在,那么已經序列化
if
( s.data ) {
//
添加到cacheURL中
cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) +
s.data );
//
刪除掉則后面就不會被發送出去了
delete
s.data;
}
//
看看是否需要避免數據從緩存中讀取
if
( s.cache ===
false
) {
s.url
= rts.test( cacheURL ) ?
//
如果已經有_參數,那么設置他的值
cacheURL.replace( rts, "$1_=" + ajax_nonce++
) :
//
否則添加一個_ = xxx在URL后面
cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++
;
}
}
//
看看需不需要設置If-Modified-Since和If-None-Match頭信息
if
( s.ifModified ) {
if
( jQuery.lastModified[ cacheURL ] ) {
jqXHR.setRequestHeader(
"If-Modified-Since"
, jQuery.lastModified[ cacheURL ] );
}
if
( jQuery.etag[ cacheURL ] ) {
jqXHR.setRequestHeader(
"If-None-Match"
, jQuery.etag[ cacheURL ] );
}
}
//
如果數據需要被發送,設置正確的頭
if
( s.data && s.hasContent && s.contentType !==
false
||
options.contentType ) {
jqXHR.setRequestHeader(
"Content-Type"
, s.contentType );
}
//
根據dataType,設置一個Accept頭
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 ] );
}
//
通過beforeSend檢查是否需要發送
if
( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) ===
false
|| state === 2
) ) {
//
終止
return
jqXHR.abort();
}
//
此時abort函數不是取消ajax,而是中斷了ajax
strAbort = "abort"
;
//
在jqXHR上綁定成功、錯誤、完成回調函數
for
( i
in
{ success: 1, error: 1, complete: 1
} ) {
jqXHR[ i ]( s[ i ] );
}
//
得到transport
transport =
inspectPrefiltersOrTransports( transports, s, options, jqXHR );
//
如果沒有,自動終止
if
( !
transport ) {
done(
-1, "No Transport"
);
}
else
{
//
否則,設置reayState為1
jqXHR.readyState = 1
;
//
如果需要,對特定對象觸發全局事件ajaxSend
if
( fireGlobals ) {
globalEventContext.trigger(
"ajaxSend"
, [ jqXHR, s ] );
}
//
如果是異步,并且設置了超時
if
( s.async && s.timeout > 0
) {
//
設置超時
timeoutTimer = setTimeout(
function
() {
jqXHR.abort(
"timeout"
);
}, s.timeout );
}
try
{
//
設置state為1
state = 1
;
//
開始發送
transport.send( requestHeaders, done );
}
catch
( e ) {
//
截獲錯誤,如果ajax未完成
if
( state < 2
) {
done(
-1
, e );
//
完成了就直接拋出錯誤
}
else
{
throw
e;
}
}
}
//
完成時的回調函數
function
done( status, nativeStatusText, responses, headers ) {
var
isSuccess, success, error, response, modified,
statusText
=
nativeStatusText;
//
如果已經調用過該函數,直接退出
if
( state === 2
) {
return
;
}
//
設置現在狀態已完成
state = 2
;
//
清除超時設置
if
( timeoutTimer ) {
clearTimeout( timeoutTimer );
}
//
不管jqXHR對象要被用到何時,
//
釋放transport的引用使得他可以先被垃圾回收
transport =
undefined;
//
緩存響應頭
responseHeadersString = headers || ""
;
//
設置readyState
jqXHR.readyState = status > 0 ? 4 : 0
;
//
得到響應數據
if
( responses ) {
//
通過ajaxHandleResponses處理數據
response =
ajaxHandleResponses( s, jqXHR, responses );
}
//
If successful, handle type chaining
//
如果成功
if
( status >= 200 && status < 300 || status === 304
) {
//
看看是否需要緩存If-Modified-Since和If-None-Match頭
if
( s.ifModified ) {
//
取得Last-Modified
modified = jqXHR.getResponseHeader("Last-Modified"
);
//
如果Last-Modified存在
if
( modified ) {
//
在jQuery.lastModified[cacheURL]保存Last-Modified
jQuery.lastModified[ cacheURL ] =
modified;
}
//
取得etag
modified = jqXHR.getResponseHeader("etag"
);
//
如果etag存在
if
( modified ) {
//
在jQuery.etag[cacheURL]緩存etag
jQuery.etag[ cacheURL ] =
modified;
}
}
//
如果沒有修改
if
( status === 304
) {
//
設置成功
isSuccess =
true
;
//
設置狀態為notmodified
statusText = "notmodified"
;
//
否則得到必要的數據
}
else
{
isSuccess
=
ajaxConvert( s, response );
statusText
=
isSuccess.state;
success
=
isSuccess.data;
error
=
isSuccess.error;
isSuccess
= !
error;
}
//
如果失敗
}
else
{
//
從statusText獲取error狀態
//
在設置statusText成"error"
//
并改變status為0
error =
statusText;
if
( status || !
statusText ) {
statusText
= "error"
;
if
( status < 0
) {
status
= 0
;
}
}
}
//
開始設置山寨xhr對象jqXHR的status和statusText
jqXHR.status =
status;
jqXHR.statusText
= ( nativeStatusText || statusText ) + ""
;
//
根據成功還是失敗,對deferred進行回調
if
( isSuccess ) {
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
}
else
{
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
}
//
根據目前statusCode回調
jqXHR.statusCode( statusCode );
statusCode
=
undefined;
//
如果需要觸發全局事件
if
( fireGlobals ) {
//
對指定元素觸發事件ajaxSuccess或者ajaxError
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError"
,
[ jqXHR, s, isSuccess
?
success : error ] );
}
//
回調完成后的Callbacks隊列
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
//
如果需要觸發全局事件
if
( fireGlobals ) {
//
對指定元素觸發事件ajaxComplete
globalEventContext.trigger( "ajaxComplete"
, [ jqXHR, s ] );
//
該ajax觸發完畢,標記active減1,如果為0,證明所有ajax結束
if
( !( --
jQuery.active ) ) {
//
觸發ajaxStop事件
jQuery.event.trigger("ajaxStop"
);
}
}
}
//
返回山寨xhr
return
jqXHR;
};
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

