1. 程式人生 > >JQuery3.1.1原始碼解讀(十九)【ajax】

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 的簡單用法:

  1. 通過 new XMLHttpRequest() 建立一個 http 請求物件;
  2. open 函式的作用是設定要開啟的 url 和型別,建立一個連線,但此時請求並沒有傳送;
  3. setRequestHeader 來設定請求頭資訊;
  4. send 函式像伺服器傳送資料請求;
  5. onreadystatechange 是一個監聽函式,當 readyState 改變的時候執行,1-2-3-4,4 表示成功返回。xhr.responseText 是返回的響應資料,很明顯,這裡是 json 格式,實際要通過響應頭來判斷,這裡省去了這一步,getAllResponseHeaders 可以獲得所有響應頭;
  6. 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 中有兩個屬性,分別是 ajaxPrefilterajaxTransport,它們是由 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,不想去深入研究了,最近暑假實習校招已經開始啟動了,暫時先放一放吧,以後有時間再來填坑吧。

參考