jQuery ajax —— 主函數分析


由於jQuery ajax對Callbacks、Deferred、serialize、event等模塊的依賴,建議對這些模塊沒有認識的朋友看一下jQuery CallbacksjQuery DeferredjQuery serializejQuery 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();

大家覺得哪個好呢?

變成上面的樣子主要是因為要解決下面兩個問題:

  1. jqXHR由於是暴露在外的,他不能包含deferred和completeDeferred,不允許用戶在外部操作這兩個對象的狀態。
  2. 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 ] + "//" );

    // 定義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;
};

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM