jQuery ajax 源碼分析之預處理和分發函數(prefilter/transport)

調用jQuery 的狀況下,咱們一般用來請求數據的方法有javascript

  • $(element).load(url, callback)
  • $.get(url, data, callback, type)
  • $.post(url, data, callback, type)
  • $.getJSON(url, data, callback)
  • $.getScript(url, callback)
  • $.ajax(options)

前五種方法,在jQuery 的實現中,本質上仍是在調用第六種方法實現的html

$().load() -> jQuery.fn.load -> $.ajax()
$.get() -> jQuery.each( [ "get", "post" ], function() {}) -> $.ajax()
$.post() -> jQuery.each( [ "get", "post" ], function() {}) -> $.ajax()
$.getJSON -> getJSON() -> $.get() -> $.ajax()
$.getScript -> getScript() -> $.get() -> $.ajax()

單純在源碼中看前五個函數,代碼量都不多,多一點也就是$().load() 函數,涉及到了deferred 的寫法,在調用成功時,對返回的數據使用內部方法html() 進行渲染,可是代碼也不難看懂java

下面重點介紹$().ajax() 方法,ajax 內部結構爲ajax

jQuery.extend({ 
    active // 調用ajax的次數
    lastModified // 緩存標識
    etag // 緩存標識
    ajaxSettings // 默認參數設置
    ajaxSetup // 開發者自定義參數與默認參數複製到當前ajax的參數中
    ajaxPrefilter // 觸發ajax調用前的預處理函數
    ajaxTransport // 觸發ajax調用後的處理函數
    ajax // ajax的主函數
    getJSON  // getJSON 函數
    getScript // getScript 函數
})

重點須要介紹的函數有三個ajaxPrefilterajaxTransport , ajax json

ajaxPrefilter 函數經過addToPrefiltersOrTransports( prefilters ) 實現的,看下jQuery 經過ajaxPrefilter 作了哪些操做跨域

  • jQuery.ajaxPrefilter( "script", function( s ) {}) 若是dataTypescript 的話,設置緩存操做,若是是跨域的話,type 就必須設置爲get
  • jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {}) 若是dataTypejson jsonp 的話,就須要作進一步的操做了瀏覽器

    // Detect, normalize options and install callbacks for jsonp requests
    jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
    
    var callbackName, overwritten, responseContainer,
      // 判斷json是在url中仍是在form中
      jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
          "url" :
          typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
      );
    
    // Handle iff the expected data type is "jsonp" or we have a parameter to set
    if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
    
      // Get callback name, remembering preexisting value associated with it
      // 若是jsonp的callback是個函數的話,就執行完函數後返回結果,若是不是函數,就返回自身
      callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
          s.jsonpCallback() :
          s.jsonpCallback;
    
      // Insert callback into url or form data
      // 把callback插入到form表單中,若是使用jsonp的話,就把callback放到url的後面
      if ( jsonProp ) {
          s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
      } else if ( s.jsonp !== false ) {
          s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
      }
    
      // Use data converter to retrieve json after script execution
      // responseContainer爲衝送到url的數據
      s.converters["script json"] = function() {
          if ( !responseContainer ) {
              jQuery.error( callbackName + " was not called" );
          }
          // 返回 jsonp 的callback的參數
          return responseContainer[ 0 ];
      };
    
      // force json dataType
      s.dataTypes[ 0 ] = "json";
    
      // Install callback
      // responseContainer 拿到 callback 函數中的參數,就是觸發 callback 函數的時,傳遞給 callback 的參數
      // ajax 觸發成功後,success中的第一個參數就是 傳遞給 callback 的參數
      overwritten = window[ callbackName ];
      window[ callbackName ] = function() {
          responseContainer = arguments;
      };
    
      // Clean-up function (fires after converters)
      jqXHR.always(function() {
          // Restore preexisting value
          window[ callbackName ] = overwritten;
    
          // Save back as free
          if ( s[ callbackName ] ) {
              // make sure that re-using the options doesn't screw things around
              s.jsonpCallback = originalSettings.jsonpCallback;
    
              // save the callback name for future use
              oldCallbacks.push( callbackName );
          }
    
          // Call if it was a function and we have a response
          // 觸發 callback 函數
          if ( responseContainer && jQuery.isFunction( overwritten ) ) {
              overwritten( responseContainer[ 0 ] );
          }
          // 清空 callback 函數
          responseContainer = overwritten = undefined;
      });
    
      // Delegate to script
      // 注意,這裏返回script,代表會使用script的方法處理
      return "script";
    }
    });

而後再看addToPrefiltersOrTransports() 函數,會根據每種dataType 存儲對應的函數緩存

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
// 添加 prefilters(預處理器)/transports(分發處理器) 裏面的函數
function addToPrefiltersOrTransports( structure ) {

    // dataTypeExpression is optional and defaults to "*"
    return function( dataTypeExpression, func ) {

        // 這是在操做原生的 Ajax 的時候,沒有傳入 dataTypeExpression
        if ( typeof dataTypeExpression !== "string" ) {
            func = dataTypeExpression;
            dataTypeExpression = "*";
        }

        var dataType,
            i = 0,
            dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];

        if ( jQuery.isFunction( func ) ) {
            // For each dataType in the dataTypeExpression
            while ( (dataType = dataTypes[i++]) ) {
                // Prepend if requested
                // 有+,說明有多個回調,就把回調往前加
                if ( dataType[0] === "+" ) {
                    dataType = dataType.slice( 1 ) || "*";
                    (structure[ dataType ] = structure[ dataType ] || []).unshift( func );

                // Otherwise append
                // 沒有多個回調,就日後添加
                } else {
                    (structure[ dataType ] = structure[ dataType ] || []).push( func );
                }
            }
        }
    };
}

而後咱們再看ajaxTransport 函數,該函數也是經過addToPrefiltersOrTransports() 函數來存儲某種dataType 類型對應的函數app

// Bind script tag hack transport
// 若是有跨域的話,就會有返回值,沒有跨域的話,就沒有返回值
// 這時候到 inpectPrefiltersOrTransports 函數中看 return !(selected = dataTypeOrTransport) 中的 dataTypeOrTransport 是有值的
jQuery.ajaxTransport( "script", function( s ) {
    // This transport only deals with cross domain requests
    // 若是須要跨域的狀況下
    if ( s.crossDomain ) {
        var script, callback;
        return {
            // send函數爲在請求的url中建立script標籤
            send: function( _, complete ) {
                // 跨域的操做,就是動態建立script
                script = jQuery("<script>").prop({
                    async: true,
                    charset: s.scriptCharset,
                    src: s.url
                }).on(
                    // 若是加載錯誤的狀況下,就把建立的script移除,並返回結果
                    "load error",
                    callback = function( evt ) {
                        script.remove();
                        callback = null;
                        if ( evt ) {
                            complete( evt.type === "error" ? 404 : 200, evt.type );
                        }
                    }
                );
                document.head.appendChild( script[ 0 ] );
            },
            // 若是中途撤銷 ajax,若是有callback就執行callback函數
            abort: function() {
                if ( callback ) {
                    callback();
                }
            }
        };
    }
});

總共有兩處使用ajaxTransport 函數,第二處沒有dataType, 咱們在addPrefiltersOrTransports() 中能看到,若是隻傳遞一個參數,那麼dataType 就爲* ,這時候處理的就是,調用原生ajax 來發送數據cors

jQuery.ajaxTransport(function( options ) {
    var callback;
    // Cross domain only allowed if supported through XMLHttpRequest
    // 不跨域的時候,就是調原生的 ajax
    if ( jQuery.support.cors || xhrSupported && !options.crossDomain ) {
        return {
            send: function( headers, complete ) {
                var i, id,
                    xhr = options.xhr();
                xhr.open( options.type, options.url, options.async, options.username, options.password );
                // 省略了部分代碼
                // Callback
                callback = function( type ) {
                    return function() {
                        // 省略了部分代碼
                };
                // Listen to events
                xhr.onload = callback(); // 沒有采用 onreadystatechange(傳統是採用這種方式),全部高級的瀏覽器都支持 onload
                // 省略了部分代碼
                // Do send the request
                // This may raise an exception which is actually
                // handled in jQuery.ajax (so no try/catch here)
                xhr.send( options.hasContent && options.data || null );
            },
            abort: function() {
                if ( callback ) {
                    callback();
                }
            }
        };
    }
});
相關文章
相關標籤/搜索