jQuery ajax 源碼分析二之ajax主函數

上文咱們已經介紹了ajax 的幾個副函數ajaxPrefilterajaxTransport ,本文主要介紹ajax 主函數的內部實現javascript

咱們通常調用ajax 有三種寫法php

// 第一種寫法
$.ajax({
  url:url,
  data:{...},
  ...
  success:function(){},
  error:function(){}
})
// 第二種寫法
$.ajax(url, {
  data:{...},
  ...
  success:function(){},
  error:function(){}
})
// 第三種寫法,也就是deferred的寫法
$.ajax(url, {
  data:{...},
  ...
}).done().fail();

第一種和第二種僅僅是url 的位置不一樣,ajax 內部會判斷傳入ajax 的第一個參數是不是對象來進行判斷html

// If url is an object, simulate pre-1.5 signature
// 使用 $.ajax({url:url,data:data}) 的寫法,須要轉換成 $.ajax(url, {data:data}); 的寫法
if ( typeof url === "object" ) {
  options = url;
  url = undefined;
}

ajax 內部經過新增jqXHR 對象來增長ajax 的功能,例如statusCode 根據ajax 中設置相應的http 狀態碼對象的函數來實現當響應的狀態碼對應到設置的狀態碼時,觸發相應的函數java

// Fake xhr
// 模擬出來的 ajax ,增長原生ajax的功能
jqXHR = {
    readyState: 0,
    // Builds headers hashtable if needed
    getResponseHeader: function( key ) {},
    // Raw string
    getAllResponseHeaders: function() {},
    // Caches the header
    setRequestHeader: function( name, value ) {    },
    // Overrides response content-type header
    overrideMimeType: function( type ) {},
    // Status-dependent callbacks
    // 狀態碼對應後,如何觸發相關的函數
    statusCode: function( map ) {},
    // Cancel the request
    // ajax時間超過timeout響應的時間時,就觸發 abort 函數
    abort: function( statusText ) {}
};

ajax 的第三種寫法,就是經過將jqXHR 添加到deferred 中,因此deferred 的全部方法,ajax 也能夠一樣使用node

// Deferreds
// ajax 可使用兩種方式來寫,鏈式調用的話,就須要使用 deferreds ,這樣就可以觸發是不是 done仍是其餘狀態來觸發相應的事件
deferred = jQuery.Deferred(),
completeDeferred = jQuery.Callbacks("once memory"),
// Attach deferreds
// 把deferred的方法,添加到 模擬的 jqXHR 中
deferred.promise( jqXHR ).complete = completeDeferred.add;
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;

上一文中介紹了ajaxaddPrefilters 函數,在ajax 發送數據以前,會經過inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); 調用全部的預處理函數,發送數據時,經過transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); 來發送數據,transports 內會根據是否跨域選擇哪一個send/abort 函數,返回的結果,經過done() 函數進行處理jquery

// Main method
    ajax: function( url, options ) {

        // If url is an object, simulate pre-1.5 signature
        // 使用 $.ajax({url:url,data:data}) 的寫法,須要轉換成 $.ajax(url, {data:data}); 的寫法
        if ( typeof url === "object" ) {
            options = url;
            url = undefined;
        }

        // Force options to be an object
        options = options || {};

        var transport,
            // URL without anti-cache param
            cacheURL,
            // Response headers
            responseHeadersString,
            responseHeaders,
            // timeout handle
            timeoutTimer,
            // Cross-domain detection vars
            parts,
            // To know if global events are to be dispatched
            fireGlobals,
            // Loop variable
            i,
            // Create the final options object
            s = jQuery.ajaxSetup( {}, options ), // 經過把options的參數放到一個新對象中,這樣就全部的參數都只會影響當前的ajax
            // Callbacks context
            callbackContext = s.context || s, // 執行的上下文,根據命名,應該是回調函數的上下文
            // Context for global events is callbackContext if it is a DOM node or jQuery collection
            // 這裏應該是觸發 ajax 的全局事件,若是有設置上下文context,那麼就可以使用 jQuery( callbackContext ) ,也就是某個元素來綁定
            // 不然,就使用默認的 jQuery底層的event來調用
            globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
                jQuery( callbackContext ) :
                jQuery.event,
            // Deferreds
            // ajax 可使用兩種方式來寫,鏈式調用的話,就須要使用 deferreds ,這樣就可以觸發是不是 done仍是其餘狀態來觸發相應的事件
            deferred = jQuery.Deferred(),
            completeDeferred = jQuery.Callbacks("once memory"),
            // Status-dependent callbacks
            // 狀態碼對應的回調操做,和success 事件同級別 statusCode: {404:function(){alert('頁面不存在')}
            statusCode = s.statusCode || {},
            // Headers (they are sent all at once)
            requestHeaders = {},
            requestHeadersNames = {},
            // The jqXHR state
            state = 0,
            // Default abort message
            strAbort = "canceled",
            // Fake xhr
            // 模擬出來的 ajax ,原生的還不夠強大
            jqXHR = {
                readyState: 0,

                // Builds headers hashtable if needed
                getResponseHeader: function( key ) {
                    var match;
                    if ( state === 2 ) {
                        if ( !responseHeaders ) {
                            responseHeaders = {};
                            while ( (match = rheaders.exec( responseHeadersString )) ) {
                                responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
                            }
                        }
                        match = responseHeaders[ key.toLowerCase() ];
                    }
                    return match == null ? null : match;
                },

                // Raw string
                getAllResponseHeaders: function() {
                    return state === 2 ? responseHeadersString : null;
                },

                // Caches the header
                setRequestHeader: function( name, value ) {
                    var lname = name.toLowerCase();
                    if ( !state ) {
                        name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
                        requestHeaders[ name ] = value;
                    }
                    return this;
                },

                // Overrides response content-type header
                overrideMimeType: function( type ) {
                    if ( !state ) {
                        s.mimeType = type;
                    }
                    return this;
                },

                // Status-dependent callbacks
                // 狀態碼對應後,如何觸發相關的函數
                statusCode: function( map ) {
                    var code;
                    if ( map ) {
                        if ( state < 2 ) {
                            for ( code in map ) {
                                // Lazy-add the new callback in a way that preserves old ones
                                statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
                            }
                        } else {
                            // Execute the appropriate callbacks
                            jqXHR.always( map[ jqXHR.status ] );
                        }
                    }
                    return this;
                },

                // Cancel the request
                // ajax時間超過timeout響應的時間時,就觸發 abort 函數
                abort: function( statusText ) {
                    var finalText = statusText || strAbort;
                    if ( transport ) {
                        transport.abort( finalText );
                    }
                    done( 0, finalText );
                    return this;
                }
            };

        // Attach deferreds
        // 把deferred的方法,添加到 模擬的 jqXHR 中
        deferred.promise( jqXHR ).complete = completeDeferred.add;
        jqXHR.success = jqXHR.done;
        jqXHR.error = jqXHR.fail;

        // Remove hash character (#7531: and string promotion)
        // Add protocol if not provided (prefilters might expect it)
        // Handle falsy url in the settings object (#10093: consistency with old signature)
        // We also use the url parameter if available
        // 把地址中 hash 設置爲空,由於在ajax中,hash值是沒有用處的,若是地址中有//,就替換成 http:// 這樣的標準寫法
        s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" )
            .replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); // 若是地址寫成 //data.php 就會轉換成 http://data.php

        // Alias method option to type as per ticket #12004
        // get/post 也可使用 method 或者是 type
        s.type = options.method || options.type || s.method || s.type;

        // Extract dataTypes list
        // text html json 若是這樣寫 dataType,就須要轉換成一個集合
        s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];

        // A cross-domain request is in order when we have a protocol:host:port mismatch
        // 檢測是否跨域
        if ( s.crossDomain == null ) {
            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" ) ) )
            );
        }

        // Convert data if not already a string
        // 處理使用 ajax 傳遞的數據
        if ( s.data && s.processData && typeof s.data !== "string" ) {
            s.data = jQuery.param( s.data, s.traditional );
        }

        // Apply prefilters,
        // 這時,觸發全部的預處理函數
        // s 就是 ajax 全部的參數, options 開發者傳進來的參數
        inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

        // If request was aborted inside a prefilter, stop there
        // 若是在 prefilter 裏面的請求就被終止了,就放回當前對象
        if ( state === 2 ) {
            return jqXHR;
        }

        // We can fire global events as of now if asked to
        fireGlobals = s.global;

        // Watch for a new set of requests
        // 全局事件觸發,這時候沒有使用具體的元素,直接使用的默認是 document 觸發 $(document).on("ajaxStart",function(){}),而不是$("div").on("ajaxStart",function(){})
        if ( fireGlobals && jQuery.active++ === 0 ) {
            jQuery.event.trigger("ajaxStart");
        }

        // Uppercase the type
        s.type = s.type.toUpperCase();

        // Determine if request has content
        // rnoContent 爲 get/head ,就是使用 ajax 的時候,把數據加到網址的後面
        s.hasContent = !rnoContent.test( s.type );

        // Save the URL in case we're toying with the If-Modified-Since
        // and/or If-None-Match header later on
        cacheURL = s.url;

        // More options handling for requests with no content
        if ( !s.hasContent ) {

            // If data is available, append data to url
            if ( s.data ) {
                cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
                // #9682: remove data so that it's not used in an eventual retry
                delete s.data;
            }

            // Add anti-cache in url if needed
            // 若是不須要緩存,就會在url的後面加上時間戳,這樣每次請求都是一次新的請求
            if ( s.cache === false ) {
                s.url = rts.test( cacheURL ) ?

                    // If there is already a '_' parameter, set its value
                    cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :

                    // Otherwise add one to the end
                    cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
            }
        }

        // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
        // 若是數據沒有變,就使用緩存的數據
        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 ] );
            }
        }

        // Set the correct header, if data is being sent
        if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
            jqXHR.setRequestHeader( "Content-Type", s.contentType );
        }

        // Set the Accepts header for the server, depending on the dataType
        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[ "*" ]
        );

        // Check for headers option
        for ( i in s.headers ) {
            jqXHR.setRequestHeader( i, s.headers[ i ] );
        }

        // Allow custom headers/mimetypes and early abort
        // 若是中止 ajax 事件,就直接調用 abort 事件
        if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
            // Abort if not done already and return
            return jqXHR.abort();
        }

        // aborting is no longer a cancellation
        strAbort = "abort";

        // Install callbacks on deferreds
        // 把 success 這些事件映射到 jqXHR 中
        for ( i in { success: 1, error: 1, complete: 1 } ) {
            jqXHR[ i ]( s[ i ] );
        }

        // Get transport
        // 觸發回調,ajax對象的send/abort方法,若是跨域,就在url內建立script的方法,若是不跨域,就使用經過原生ajax封裝好的send/abort方法
        transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

        // If no transport, we auto-abort
        if ( !transport ) {
            done( -1, "No Transport" );
        } else {
            jqXHR.readyState = 1;

            // Send global event
            if ( fireGlobals ) {
                globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
            }
            // Timeout
            // 若是請求的時間大於設置的timeout,就調用 abort 函數
            if ( s.async && s.timeout > 0 ) {
                timeoutTimer = setTimeout(function() {
                    jqXHR.abort("timeout");
                }, s.timeout );
            }

            try {
                state = 1;
                // 調用transport封裝好的 send 發送
                transport.send( requestHeaders, done );
            } catch ( e ) {
                // Propagate exception as error if not done
                if ( state < 2 ) {
                    done( -1, e );
                // Simply rethrow otherwise
                } else {
                    throw e;
                }
            }
        }

        // Callback for when everything is done
        function done( status, nativeStatusText, responses, headers ) {
            var isSuccess, success, error, response, modified,
                statusText = nativeStatusText;

            // Called once
            if ( state === 2 ) {
                return;
            }

            // State is "done" now
            state = 2;

            // Clear timeout if it exists
            if ( timeoutTimer ) {
                clearTimeout( timeoutTimer );
            }

            // Dereference transport for early garbage collection
            // (no matter how long the jqXHR object will be used)
            transport = undefined;

            // Cache response headers
            responseHeadersString = headers || "";

            // Set readyState
            jqXHR.readyState = status > 0 ? 4 : 0;

            // Determine if successful
            isSuccess = status >= 200 && status < 300 || status === 304;

            // Get response data
            // 而後jquery進行處理,獲得合適的輸出,能夠經過使用 console.log來打印出 ajax 返回的數據,來看出是怎麼處理的數據
            // console.log(responses)
            if ( responses ) {
                response = ajaxHandleResponses( s, jqXHR, responses );
            }
            // console.log(responses)
            // Convert no matter what (that way responseXXX fields are always set)
            response = ajaxConvert( s, response, jqXHR, isSuccess );
            // console.log(responses)

            // If successful, handle type chaining
            if ( isSuccess ) {

                // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
                // 是否須要使用緩存中的數據
                if ( s.ifModified ) {
                    modified = jqXHR.getResponseHeader("Last-Modified");
                    if ( modified ) {
                        jQuery.lastModified[ cacheURL ] = modified;
                    }
                    modified = jqXHR.getResponseHeader("etag");
                    if ( modified ) {
                        jQuery.etag[ cacheURL ] = modified;
                    }
                }

                // if no content
                if ( status === 204 || s.type === "HEAD" ) {
                    statusText = "nocontent";

                // if not modified
                } else if ( status === 304 ) {
                    statusText = "notmodified";

                // If we have data, let's convert it
                } else {
                    statusText = response.state;
                    success = response.data;
                    error = response.error;
                    isSuccess = !error;
                }
            } else {
                // We extract error from statusText
                // then normalize statusText and status for non-aborts
                error = statusText;
                if ( status || !statusText ) {
                    statusText = "error";
                    if ( status < 0 ) {
                        status = 0;
                    }
                }
            }

            // Set data for the fake xhr object
            jqXHR.status = status;
            jqXHR.statusText = ( nativeStatusText || statusText ) + "";

            // Success/Error
            // 經過返回的數據是否成功,來觸發deferred 的resolveWith和rejectWith函數
            if ( isSuccess ) {
                deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
            } else {
                deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
            }

            // Status-dependent callbacks
            // 調用ajax返回的狀態碼,來觸發開發者本身設置的http狀態碼的函數,
            jqXHR.statusCode( statusCode );
            statusCode = undefined;

            // 若是設置了全局函數,根據ajax是否調用成功,來選擇觸發ajaxSuccess函數仍是ajaxError函數
            if ( fireGlobals ) {
                globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
                    [ jqXHR, s, isSuccess ? success : error ] );
            }

            // Complete
            completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

            // ajax 調用完成,觸發全局函數 ajaxComplete,若是當前頁所有的ajax都調用完成,就觸發全局函數ajaxStop
            if ( fireGlobals ) {
                globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
                // Handle the global AJAX counter
                if ( !( --jQuery.active ) ) {
                    jQuery.event.trigger("ajaxStop");
                }
            }
        }
        // 整個 ajax 調用完後,返回的是 jqXHR 對象,就可使用deferred的一系列方法 done,fail 等方法了
        return jqXHR;
    },

待續...ajax

相關文章
相關標籤/搜索