成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

讀Zepto源碼之Ajax模塊

Crazy_Coder / 3165人閱讀

摘要:私有變量用來臨時存放配置中的,即請求成功后執(zhí)行的回調(diào)函數(shù)名,該配置可以為類型。是根據(jù)配置得出的回調(diào)函數(shù)名。接下來,將的占位符,替換成回調(diào)函數(shù)名,最后將插入到頁面中,發(fā)送請求。

Ajax 模塊也是經(jīng)常會用到的模塊,Ajax 模塊中包含了 jsonp 的現(xiàn)實,和 XMLHttpRequest 的封裝。

讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto

源碼版本

本文閱讀的源碼為 zepto1.2.0

ajax的事件觸發(fā)順序

zepto 針對 ajax 的發(fā)送過程,定義了以下幾個事件,正常情況下的觸發(fā)順序如下:

ajaxstart : XMLHttpRequest 實例化前觸發(fā)

ajaxBeforeSend: 發(fā)送 ajax 請求前觸發(fā)

ajaxSend : 發(fā)送 ajax 請求時觸發(fā)

ajaxSuccess / ajaxError : 請求成功/失敗時觸發(fā)

ajaxComplete: 請求完成(無論成功還是失敗)時觸發(fā)

ajaxStop: 請求完成后觸發(fā),這個事件在 ajaxComplete 后觸發(fā)。

ajax 方法的參數(shù)解釋

現(xiàn)在還沒有講到 ajax 方法,之所以要將參數(shù)提前,是因為后面的內(nèi)容,不時會用到相關(guān)的參數(shù),所以一開始先將參數(shù)解釋清楚。

typeHTTP 請求的類型;

url: 請求的路徑;

data: 請求參數(shù);

processData: 是否需要將非 GET 請求的參數(shù)轉(zhuǎn)換成字符串,默認(rèn)為 true ,即默認(rèn)轉(zhuǎn)換成字符串;

contentType: 設(shè)置 Content-Type 請求頭;

mineType : 覆蓋響應(yīng)的 MIME 類型,可以是 json、 jsonp、 script、 xmlhtml、 或者 text

jsonp: jsonp 請求時,攜帶回調(diào)函數(shù)名的參數(shù)名,默認(rèn)為 callback;

jsonpCallbackjsonp 請求時,響應(yīng)成功時,執(zhí)行的回調(diào)函數(shù)名,默認(rèn)由 zepto 管理;

timeout: 超時時間,默認(rèn)為 0;

headers:設(shè)置 HTTP 請求頭;

async: 是否為同步請求,默認(rèn)為 false;

global: 是否觸發(fā)全局 ajax 事件,默認(rèn)為 true;

context: 執(zhí)行回調(diào)時(如 jsonpCallbak)時的上下文環(huán)境,默認(rèn)為 window

traditional: 是否使用傳統(tǒng)的淺層序列化方式序列化 data 參數(shù),默認(rèn)為 false,例如有 data{p1:"test1", p2: {nested: "test2"} ,在 traditionalfalse 時,會序列化成 p1=test1&p2[nested]=test2, 在為 true 時,會序列化成 p1=test&p2=[object+object];

xhrFieldsxhr 的配置;

cache:是否允許瀏覽器緩存 GET 請求,默認(rèn)為 false;

username:需要認(rèn)證的 HTTP 請求的用戶名;

password: 需要認(rèn)證的 HTTP 請求的密碼;

dataFilter: 對響應(yīng)數(shù)據(jù)進(jìn)行過濾;

xhrXMLHttpRequest 實例,默認(rèn)用 new XMLHttpRequest() 生成;

accepts:從服務(wù)器請求的 MIME 類型;

beforeSend: 請求發(fā)出前調(diào)用的函數(shù);

success: 請求成功后調(diào)用的函數(shù);

error: 請求出錯時調(diào)用的函數(shù);

complete: 請求完成時調(diào)用的函數(shù),無論請求是失敗還是成功。

內(nèi)部方法 triggerAndReturn
function triggerAndReturn(context, eventName, data) {
  var event = $.Event(eventName)
  $(context).trigger(event, data)
  return !event.isDefaultPrevented()
}

triggerAndReturn 用來觸發(fā)一個事件,并且如果該事件禁止瀏覽器默認(rèn)事件時,返回 false。

參數(shù) context 為上下文,eventName 為事件名,data 為數(shù)據(jù)。

該方法內(nèi)部調(diào)用了 Event 模塊的 trigger 方法,具體分析見《讀Zepto源碼之Event模塊》。

triggerGlobal
function triggerGlobal(settings, context, eventName, data) {
  if (settings.global) return triggerAndReturn(context || document, eventName, data)
}

觸發(fā)全局事件

settingsajax 配置,context 為指定的上下文對象,eventName 為事件名,data 為數(shù)據(jù)。

triggerGlobal 內(nèi)部調(diào)用的是 triggerAndReturn 方法,如果有指定上下文對象,則在指定的上下文對象上觸發(fā),否則在 document 上觸發(fā)。

ajaxStart
function ajaxStart(settings) {
  if (settings.global && $.active++ === 0) triggerGlobal(settings, null, "ajaxStart")
}

觸發(fā)全局的 ajaxStart 事件。

如果 global 設(shè)置為 true,則 $.active 的值增加1。

如果 globaltrue ,并且 $.active 在更新前的數(shù)量為 0,則觸發(fā)全局的 ajaxStart 事件。

ajaxStop
function ajaxStop(settings) {
  if (settings.global && !(--$.active)) triggerGlobal(settings, null, "ajaxStop")
}

觸發(fā)全局 ajaxStop 事件。

如果 globaltrue ,則將 $.active 的數(shù)量減少 1。如果 $.active 的數(shù)量減少至 0,即沒有在執(zhí)行中的 ajax 請求時,觸發(fā)全局的 ajaxStop 事件。

ajaxBeforeSend
function ajaxBeforeSend(xhr, settings) {
  var context = settings.context
  if (settings.beforeSend.call(context, xhr, settings) === false ||
      triggerGlobal(settings, context, "ajaxBeforeSend", [xhr, settings]) === false)
    return false

  triggerGlobal(settings, context, "ajaxSend", [xhr, settings])
}

ajaxBeforeSend 方法,觸發(fā) ajaxBeforeSend 事件和 ajaxSend 事件。

這兩個事件很相似,只不過 ajaxBeforedSend 事件可以通過外界的配置來取消事件的觸發(fā)。

在觸發(fā) ajaxBeforeSend 事件之前,會調(diào)用配置中的 beforeSend 方法,如果 befoeSend 方法返回的為 false時,則取消觸發(fā) ajaxBeforeSend 事件,并且會取消后續(xù) ajax 請求的發(fā)送,后面會講到。

否則觸發(fā) ajaxBeforeSend 事件,并且將 xhr 事件,和配置 settings 作為事件攜帶的數(shù)據(jù)。

注意這里很巧妙地使用了 || 進(jìn)行斷路。

如果 beforeSend 返回的為 false 或者觸發(fā)ajaxBeforeSend 事件的方法 triggerGlobal 返回的為 false,也即取消了瀏覽器的默認(rèn)行為,則 ajaxBeforeSend 方法返回 false,中止后續(xù)的執(zhí)行。

否則在觸發(fā)完 ajaxBeforeSend 事件后,觸發(fā) ajaxSend 事件。

ajaxComplete
function ajaxComplete(status, xhr, settings) {
  var context = settings.context
  settings.complete.call(context, xhr, status)
  triggerGlobal(settings, context, "ajaxComplete", [xhr, settings])
  ajaxStop(settings)
}

觸發(fā) ajaxComplete 事件。

在觸發(fā) ajaxComplete 事件前,調(diào)用配置中的 complete 方法,將 xhr 實例和當(dāng)前的狀態(tài) state 作為回調(diào)函數(shù)的參數(shù)。在觸發(fā)完 ajaxComplete 事件后,調(diào)用 ajaxStop 方法,觸發(fā) ajaxStop 事件。

ajaxSuccess
function ajaxSuccess(data, xhr, settings, deferred) {
  var context = settings.context, status = "success"
  settings.success.call(context, data, status, xhr)
  if (deferred) deferred.resolveWith(context, [data, status, xhr])
  triggerGlobal(settings, context, "ajaxSuccess", [xhr, settings, data])
  ajaxComplete(status, xhr, settings)
}

觸發(fā) ajaxSucess 方法。

在觸發(fā) ajaxSuccess 事件前,先調(diào)用配置中的 success 方法,將 ajax 返回的數(shù)據(jù) data 和當(dāng)前狀態(tài) statusxhr 作為回調(diào)函數(shù)的參數(shù)。

如果 deferred 存在,則調(diào)用 resoveWith 的方法,因為 deferred 對象,因此在使用 ajax 的時候,可以使用 promise 風(fēng)格的調(diào)用。關(guān)于 deferred ,見 《讀Zepto源碼之Deferred模塊》的分析。

在觸發(fā)完 ajaxSuccess 事件后,繼續(xù)調(diào)用 ajaxComplete 方法,觸發(fā) ajaxComplete 事件。

ajaxError
function ajaxError(error, type, xhr, settings, deferred) {
  var context = settings.context
  settings.error.call(context, xhr, type, error)
  if (deferred) deferred.rejectWith(context, [xhr, type, error])
  triggerGlobal(settings, context, "ajaxError", [xhr, settings, error || type])
  ajaxComplete(type, xhr, settings)
}

觸發(fā) ajaxError 事件,錯誤的類型可以為 timeout、error、 abort、 parsererror。

在觸發(fā)事件前,調(diào)用配置中的 error 方法,將 xhr 實例,錯誤類型 typeerror 對象作為回調(diào)函數(shù)的參數(shù)。

隨后調(diào)用 ajaxComplete 方法,觸發(fā) ajaxComplete 事件。因此,ajaxComplete 事件無論成功還是失敗都會觸發(fā)。

empty
function empty() {}

空函數(shù),用來作為回調(diào)函數(shù)配置的初始值。這樣的好處是在執(zhí)行回調(diào)函數(shù)時,不需要每次都判斷回調(diào)函數(shù)是否存在。

ajaxDataFilter
function ajaxDataFilter(data, type, settings) {
  if (settings.dataFilter == empty) return data
  var context = settings.context
  return settings.dataFilter.call(context, data, type)
}

主要用來過濾請求成功后的響應(yīng)數(shù)據(jù)。

如果配置中的 dataFilter 屬性為初始值 empty,則將原始數(shù)據(jù)返回。

如果有配置 dataFilter,則調(diào)用配置的回調(diào)方法,將數(shù)據(jù) data 和數(shù)據(jù)類型 type 作為回調(diào)的參數(shù),再將執(zhí)行的結(jié)果返回。

mimeToDataType
var htmlType = "text/html",
    jsonType = "application/json",
    scriptTypeRE = /^(?:text|application)/javascript/i,
    xmlTypeRE = /^(?:text|application)/xml/i,
function mimeToDataType(mime) {
  if (mime) mime = mime.split(";", 2)[0]
  return mime && ( mime == htmlType ? "html" :
                  mime == jsonType ? "json" :
                  scriptTypeRE.test(mime) ? "script" :
                  xmlTypeRE.test(mime) && "xml" ) || "text"
}

返回 dataType 的類型。

先看看這個函數(shù)中使用到的幾個正則表達(dá)式,scriptTypeRE 匹配的是 text/javascript 或者 application/javascriptxmlTypeRE 匹配的是 text/xml 或者 application/xml, 都還比較簡單,不作過多的解釋。

Content-Type 的值的形式如下 text/html; charset=utf-8, 所以如果參數(shù) mime 存在,則用 ; 分割,取第一項,這里是 text/html,即為包含類型的字符串。

接下來是針對 html 、json、 scriptxml 用對應(yīng)的正則進(jìn)行匹配,匹配成功,返回對應(yīng)的類型值,如果都不匹配,則返回 text。

appendQuery
function appendQuery(url, query) {
  if (query == "") return url
  return (url + "&" + query).replace(/[&?]{1,2}/, "?")
}

url 追加參數(shù)。

如果 query 為空,則將原 url 返回。

如果 query 不為空,則用 & 拼接 query。

最后調(diào)用 replace,將 &&?& ,&??? 替換成 ?。

拼接出來的 url 的形式如 url?key=value&key2=value

parseArguments
function parseArguments(url, data, success, dataType) {
  if ($.isFunction(data)) dataType = success, success = data, data = undefined
  if (!$.isFunction(success)) dataType = success, success = undefined
  return {
    url: url
    , data: data
    , success: success
    , dataType: dataType
  }
}

這個方法是用來格式化參數(shù)的,Ajax 模塊定義了一些便捷的調(diào)用方法,這些調(diào)用方法不需要傳遞 option,某些必填值已經(jīng)采用了默認(rèn)傳遞的方式,這些方法中有些參數(shù)是可以不需要傳遞的,這個方法就是來用判讀那些參數(shù)有傳遞,那些沒有傳遞,然后再將參數(shù)拼接成 ajax 所需要的 options 對象。

serialize
function serialize(params, obj, traditional, scope){
  var type, array = $.isArray(obj), hash = $.isPlainObject(obj)
  $.each(obj, function(key, value) {
    type = $.type(value)
    if (scope) key = traditional ? scope :
    scope + "[" + (hash || type == "object" || type == "array" ? key : "") + "]"
    // handle data in serializeArray() format
    if (!scope && array) params.add(value.name, value.value)
    // recurse into nested objects
    else if (type == "array" || (!traditional && type == "object"))
      serialize(params, value, traditional, key)
    else params.add(key, value)
  })
}

序列化參數(shù)。

要了解這個函數(shù),需要了解 traditional 參數(shù)的作用,這個參數(shù)表示是否開啟以傳統(tǒng)的淺層序列化方式來進(jìn)行序列化,具體的示例見上文參數(shù)解釋部分。

如果參數(shù) obj 的為數(shù)組,則 arraytrue, 如果為純粹對象,則 hashtrue。 $.isArray$.isPlainObject 的源碼分析見《讀Zepto源碼之內(nèi)部方法》。

遍歷需要序列化的對象 obj,判斷 value 的類型 type, 這個 type 后面會用到。

scope 是記錄深層嵌套時的 key 值,這個 key 值受 traditional 的影響。

如果 traditionaltrue ,則 key 為原始的 scope 值,即對象第一層的 key 值。

否則,用 [] 拼接當(dāng)前循環(huán)中的 key ,最終的 key 值會是這種形式 scope[key][key2]...

如果 obj 為數(shù)組,并且 scope 不存在,即為第一層,直接調(diào)用 params.add 方法,這個方法后面會分析到。

否則如果 value 的類型為數(shù)組或者非傳統(tǒng)序列化方式下為對象,則遞歸調(diào)用 serialize 方法,用來處理 key

其他情況調(diào)用 params.add 方法。

serializeData
function serializeData(options) {
  if (options.processData && options.data && $.type(options.data) != "string")
    options.data = $.param(options.data, options.traditional)
  if (options.data && (!options.type || options.type.toUpperCase() == "GET" || "jsonp" == options.dataType))
    options.url = appendQuery(options.url, options.data), options.data = undefined
}

序列化參數(shù)。

如果 processDatatrue ,并且參數(shù) data 不為字符串,則調(diào)用 $.params 方法序列化參數(shù)。 $.params 方法后面會講到。

如果為 GET 請求或者為 jsonp ,則調(diào)用 appendQuery ,將參數(shù)拼接到請求地址后面。

對外接口 $.active
$.active = 0

正在請求的 ajax 數(shù)量,初始時為 0。

$.ajaxSettings
$.ajaxSettings = {
  // Default type of request
  type: "GET",
  // Callback that is executed before request
  beforeSend: empty,
  // Callback that is executed if the request succeeds
  success: empty,
  // Callback that is executed the the server drops error
  error: empty,
  // Callback that is executed on request complete (both: error and success)
  complete: empty,
  // The context for the callbacks
  context: null,
  // Whether to trigger "global" Ajax events
  global: true,
  // Transport
  xhr: function () {
    return new window.XMLHttpRequest()
  },
  // MIME types mapping
  // IIS returns Javascript as "application/x-javascript"
  accepts: {
    script: "text/javascript, application/javascript, application/x-javascript",
    json:   jsonType,
    xml:    "application/xml, text/xml",
    html:   htmlType,
    text:   "text/plain"
  },
  // Whether the request is to another domain
  crossDomain: false,
  // Default timeout
  timeout: 0,
  // Whether data should be serialized to string
  processData: true,
  // Whether the browser should be allowed to cache GET responses
  cache: true,
  //Used to handle the raw response data of XMLHttpRequest.
  //This is a pre-filtering function to sanitize the response.
  //The sanitized response should be returned
  dataFilter: empty
}

ajax 默認(rèn)配置,這些是 zepto 的默認(rèn)值,在使用時,可以更改成自己需要的配置。

$.param
var escape = encodeURIComponent
$.param = function(obj, traditional){
  var params = []
  params.add = function(key, value) {
    if ($.isFunction(value)) value = value()
    if (value == null) value = ""
    this.push(escape(key) + "=" + escape(value))
  }
  serialize(params, obj, traditional)
  return params.join("&").replace(/%20/g, "+")
}

param 方法用來序列化參數(shù),內(nèi)部調(diào)用的是 serialize 方法,并且在容器 params 上定義了一個 add 方法,供 serialize 調(diào)用。

add 方法比較簡單,首先判斷值 value 是否為 function ,如果是,則通過調(diào)用函數(shù)來取值,如果為 null 或者 undefined ,則 value 賦值為空字符串。

然后將 keyvalueencodeURIComponent 編碼,用 = 號連接起來。

接著便是簡單的調(diào)用 serialize 方法。

最后將容器中的數(shù)據(jù)用 & 連接起來,并且將空格替換成 + 號。

$.ajaxJSONP
var jsonpID = +new Date()
$.ajaxJSONP = function(options, deferred){
  if (!("type" in options)) return $.ajax(options)

  var _callbackName = options.jsonpCallback,
      callbackName = ($.isFunction(_callbackName) ?
                      _callbackName() : _callbackName) || ("Zepto" + (jsonpID++)),
      script = document.createElement("script"),
      originalCallback = window[callbackName],
      responseData,
      abort = function(errorType) {
        $(script).triggerHandler("error", errorType || "abort")
      },
      xhr = { abort: abort }, abortTimeout

  if (deferred) deferred.promise(xhr)

  $(script).on("load error", function(e, errorType){
    clearTimeout(abortTimeout)
    $(script).off().remove()

    if (e.type == "error" || !responseData) {
      ajaxError(null, errorType || "error", xhr, options, deferred)
    } else {
      ajaxSuccess(responseData[0], xhr, options, deferred)
    }

    window[callbackName] = originalCallback
    if (responseData && $.isFunction(originalCallback))
      originalCallback(responseData[0])

    originalCallback = responseData = undefined
  })

  if (ajaxBeforeSend(xhr, options) === false) {
    abort("abort")
    return xhr
  }

  window[callbackName] = function(){
    responseData = arguments
  }

  script.src = options.url.replace(/?(.+)=?/, "?$1=" + callbackName)
  document.head.appendChild(script)

  if (options.timeout > 0) abortTimeout = setTimeout(function(){
    abort("timeout")
  }, options.timeout)

  return xhr
}

在分析源碼之前,先了解一下 jsonp 的原理。

jsonp 實現(xiàn)跨域其實是利用了 script 可以請求跨域資源的特點,所以實現(xiàn) jsonp 的基本步驟就是向頁面動態(tài)插入一個 script 標(biāo)簽,在請求地址上帶上需要傳遞的參數(shù),后端再將數(shù)據(jù)返回,前端調(diào)用回調(diào)函數(shù)進(jìn)行解釋。

所以 jsonp 本質(zhì)上是一個 GET 請求,因為鏈接的長度有限制,因此請求所攜帶的參數(shù)的長度也會有限制。

一些變量的定義
if (!("type" in options)) return $.ajax(options)

var _callbackName = options.jsonpCallback,
    callbackName = ($.isFunction(_callbackName) ?
                    _callbackName() : _callbackName) || ("Zepto" + (jsonpID++)),
    script = document.createElement("script"),
    originalCallback = window[callbackName],
    responseData,
    abort = function(errorType) {
      $(script).triggerHandler("error", errorType || "abort")
    },
    xhr = { abort: abort }, abortTimeout

if (deferred) deferred.promise(xhr)

如果配置中的請求類型沒有定義,則直接調(diào)用 $.ajax 方法,這個方法是整個模塊的核心,后面會講到。 jsonp 請求的 type 必須為 jsonp

私有變量用來臨時存放配置中的 jsonpCallback ,即 jsonp 請求成功后執(zhí)行的回調(diào)函數(shù)名,該配置可以為 function 類型。

callbackName 是根據(jù)配置得出的回調(diào)函數(shù)名。如果 _callbackNamefunction ,則以執(zhí)行的結(jié)果作為回調(diào)函數(shù)名,如果 _callbackName 沒有配置,則用 Zepto + 時間戳 作為回調(diào)函數(shù)名,時間戳初始化后,采用自增的方式來實現(xiàn)函數(shù)名的唯一性。

script 用來保存創(chuàng)建的 script 節(jié)點。

originalCallback 用來儲存原始的回調(diào)函數(shù)。

responseData 為響應(yīng)的數(shù)據(jù)。

abort 函數(shù)用來中止 jsonp 請求,實質(zhì)上是觸發(fā)了 error 事件。

xhr 對象只有 abort 方法,如果存在 deferred 對象,則調(diào)用 promise 方法在 xhr 對象的基礎(chǔ)上生成一個 promise 對象。

abortTimeout 用來指定超時時間。

beforeSend
if (ajaxBeforeSend(xhr, options) === false) {
  abort("abort")
  return xhr
}

在發(fā)送 jsonp 請求前,會調(diào)用 ajaxBeforeSend 方法,如果返回的為 false,則中止 jsonp 請求的發(fā)送。

發(fā)送請求
window[callbackName] = function(){
  responseData = arguments
}

script.src = options.url.replace(/?(.+)=?/, "?$1=" + callbackName)
document.head.appendChild(script)

發(fā)送請求前,重寫了 window[callbackName] 函數(shù),將 arguments 賦值給 responseData, 這個函數(shù)會在后端返回的 js 代碼中執(zhí)行,這樣 responseData 就可以獲取得到數(shù)據(jù)了。

接下來,將 url=? 占位符,替換成回調(diào)函數(shù)名,最后將 script 插入到頁面中,發(fā)送請求。

請求超時
if (options.timeout > 0) abortTimeout = setTimeout(function(){
  abort("timeout")
}, options.timeout)

如果有設(shè)置超時時間,則在請求超時時,觸發(fā)錯誤事件。

請求成功或失敗
$(script).on("load error", function(e, errorType){
  clearTimeout(abortTimeout)
  $(script).off().remove()

  if (e.type == "error" || !responseData) {
    ajaxError(null, errorType || "error", xhr, options, deferred)
  } else {
    ajaxSuccess(responseData[0], xhr, options, deferred)
  }

  window[callbackName] = originalCallback
  if (responseData && $.isFunction(originalCallback))
    originalCallback(responseData[0])

  originalCallback = responseData = undefined
})

在請求成功或者失敗時,先清除請求超時定時器,避免觸發(fā)超時錯誤,再將插入頁面的 script 從頁面上刪除,因為數(shù)據(jù)已經(jīng)獲取到,不再需要這個 script 了。注意在刪除 script 前,調(diào)用了 off 方法,將 script 上的事件都移除了。

如果請求出錯,則調(diào)用 ajaxError 方法。

如果請求成功,則調(diào)用 ajaxSuccess 方法。

之前我們把 window[callbackName] 重寫掉了,目的是為了獲取到數(shù)據(jù),現(xiàn)在再重新將原來的回調(diào)函數(shù)賦值回去,在獲取到數(shù)據(jù)后,如果 originalCallback 有定義,并且為函數(shù),則將數(shù)據(jù)作為參數(shù)傳遞進(jìn)去,執(zhí)行。

最后將數(shù)據(jù)和臨時函數(shù) originalCallback 清理。

$.ajax

$.ajax 方法是整個模塊的核心,代碼太長,就不全部貼在這里了,下面一部分一部分來分析。

處理默認(rèn)配置
var settings = $.extend({}, options || {}),
    deferred = $.Deferred && $.Deferred(),
    urlAnchor, hashIndex
for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
ajaxStart(settings)

settings 為所傳遞配置的副本。

deferreddeferred 對象。

urlAnchor 為瀏覽器解釋的路徑,會用來判斷是否跨域,后面會講到。

hashIndex 為路徑中 hash 的索引。

for ... in 去遍歷 $.ajaxSettings ,作為配置的默認(rèn)值。

配置處理完畢后,調(diào)用 ajaxStart 函數(shù),觸發(fā) ajaxStart 事件。

判斷是否跨域
originAnchor = document.createElement("a")
originAnchor.href = window.location.href

if (!settings.crossDomain) {
  urlAnchor = document.createElement("a")
  urlAnchor.href = settings.url
  // cleans up URL for .href (IE only), see https://github.com/madrobby/zepto/pull/1049
  urlAnchor.href = urlAnchor.href
  settings.crossDomain = (originAnchor.protocol + "http://" + originAnchor.host) !== (urlAnchor.protocol + "http://" + urlAnchor.host)
}

如果跨域 crossDomain 沒有設(shè)置,則需要檢測請求的地址是否跨域。

originAnchor 是當(dāng)前頁面鏈接,整體思路是創(chuàng)建一個 a 節(jié)點,將 href 屬性設(shè)置為當(dāng)前請求的地址,然后獲取節(jié)點的 protocolhost,看跟當(dāng)前頁面的鏈接用同樣方式拼接出來的地址是否一致。

注意到這里的 urlAnchor 進(jìn)行了兩次賦值,這是因為 ie 默認(rèn)不會對鏈接 a 添加端口號,但是會對 window.location.href 添加端口號,如果端口號為 80 時,會出現(xiàn)不一致的情況。具體見:pr#1049

處理請求地址
if (!settings.url) settings.url = window.location.toString()
if ((hashIndex = settings.url.indexOf("#")) > -1) settings.url = settings.url.slice(0, hashIndex)
serializeData(settings)

如果沒有配置 url ,則用當(dāng)前頁面的地址作為請求地址。

如果請求的地址帶有 hash, 則將 hash 去掉,因為 hash 并不會傳遞給后端。

然后調(diào)用 serializeData 方法來序列化請求參數(shù) data

處理緩存
var dataType = settings.dataType, hasPlaceholder = /?.+=?/.test(settings.url)
if (hasPlaceholder) dataType = "jsonp"

if (settings.cache === false || (
  (!options || options.cache !== true) &&
  ("script" == dataType || "jsonp" == dataType)
))
  settings.url = appendQuery(settings.url, "_=" + Date.now())

hasPlaceholder 的正則匹配規(guī)則跟上面分析到 jsonp 的替換 callbackName 的正則一樣,約定以這樣的方式來替換 url 中的 callbackName。因此,也可以用這樣的正則來判斷是否為 jsonp。

如果 cache 的配置為 false ,或者在 dataTypescript 或者 jsonp 的情況下, cache 沒有設(shè)置為 true 時,表示不需要緩存,清除瀏覽器緩存的方式也很簡單,就是往請求地址的后面加上一個時間戳,這樣每次請求的地址都不一樣,瀏覽器自然就沒有緩存了。

處理jsonp
if ("jsonp" == dataType) {
  if (!hasPlaceholder)
    settings.url = appendQuery(settings.url,
                               settings.jsonp ? (settings.jsonp + "=?") : settings.jsonp === false ? "" : "callback=?")
  return $.ajaxJSONP(settings, deferred)
}

判斷 dataType 的類型為 jsonp 時,會對 url 進(jìn)行一些處理。

如果還沒有 ?= 占位符,則向 url 中追加占位符。

如果 settings.jsonp 存在,則追加 settings.jsonp + =?

如果 settings.jsonpfalse, 則不向 url 中追加?xùn)|西。

否則默認(rèn)追加 callback=?

url 拼接完畢后,調(diào)用 $.ajaxJSONP 方法,發(fā)送 jsonp 請求。

一些變量
var mime = settings.accepts[dataType],
    headers = { },
    setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value] },
    protocol = /^([w-]+:)///.test(settings.url) ? RegExp.$1 : window.location.protocol,
    xhr = settings.xhr(),
    nativeSetHeader = xhr.setRequestHeader,
    abortTimeout

if (deferred) deferred.promise(xhr)

mime 獲取數(shù)據(jù)的 mime 類型。

headers 為請求頭。

setHeader 為設(shè)置請求頭的方法,其實是往 headers 上增加對應(yīng)的 key value 值。

protocol 為協(xié)議,匹配一個或多個以字母、數(shù)字或者 - 開頭,并且后面為 :// 的字符串。優(yōu)先從配置的 url 中獲取,如果沒有配置 url,則取 window.location.protocol。

xhrXMLHttpRequest 實例。

nativeSetHeaderxhr 實例上的 setRequestHeader 方法。

abortTimeout 為超時定時器的 id。

如果 deferred 對象存在,則調(diào)用 promise 方法,以 xhr 為基礎(chǔ)生成一個 promise 。

設(shè)置請求頭
if (!settings.crossDomain) setHeader("X-Requested-With", "XMLHttpRequest")
setHeader("Accept", mime || "*/*")
if (mime = settings.mimeType || mime) {
  if (mime.indexOf(",") > -1) mime = mime.split(",", 2)[0]
  xhr.overrideMimeType && xhr.overrideMimeType(mime)
}
if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != "GET"))
  setHeader("Content-Type", settings.contentType || "application/x-www-form-urlencoded")

if (settings.headers) for (name in settings.headers) setHeader(name, settings.headers[name])
xhr.setRequestHeader = setHeader

如果不是跨域請求時,設(shè)置請求頭 X-Requested-With 的值為 XMLHttpRequest 。這個請求頭的作用是告訴服務(wù)端,這個請求為 ajax 請求。

setHeader("Accept", mime || "*/*") 用來設(shè)置客戶端接受的資源類型。

當(dāng) mime 存在時,調(diào)用 overrideMimeType 方法來重寫 responsecontent-type ,使得服務(wù)端返回的類型跟客戶端要求的類型不一致時,可以按照指定的格式來解釋。具體可以參見這篇文章 《你真的會使用XMLHttpRequest嗎?》。

如果有指定 contentType ,

或者 contentType 沒有設(shè)置為 false ,并且 data 存在以及請求類型不為 GET 時,設(shè)置 Content-Type 為指定的 contentType ,在沒有指定時,設(shè)置為 application/x-www-form-urlencoded 。所以沒有指定 contentType 時, POST 請求,默認(rèn)的 Content-Typeapplication/x-www-form-urlencoded。

如果有配置 headers ,則遍歷 headers 配置,分別調(diào)用 setHeader 方法配置。

before send
if (ajaxBeforeSend(xhr, settings) === false) {
  xhr.abort()
  ajaxError(null, "abort", xhr, settings, deferred)
  return xhr
}

調(diào)用 ajaxBeforeSend 方法,如果返回的為 false ,則中止 ajax 請求。

同步和異步請求的處理
var async = "async" in settings ? settings.async : true
xhr.open(settings.type, settings.url, async, settings.username, settings.password)

如果有配置 async ,則采用配置中的值,否則,默認(rèn)發(fā)送的是異步請求。

接著調(diào)用 open 方法,創(chuàng)建一個請求。

創(chuàng)建請求后的配置
if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name]

for (name in headers) nativeSetHeader.apply(xhr, headers[name])

如果有配置 xhrFields ,則遍歷,設(shè)置對應(yīng)的 xhr 屬性。

再遍歷上面配置的 headers 對象,調(diào)用 setRequestHeader 方法,設(shè)置請求頭,注意這里的請求頭必須要在 open 之后,在 send 之前設(shè)置。

發(fā)送請求
xhr.send(settings.data ? settings.data : null)

發(fā)送請求很簡單,調(diào)用 xhr.send 方法,將配置中的數(shù)據(jù)傳入即可。

請求響應(yīng)成功后的處理
xhr.onreadystatechange = function(){
  if (xhr.readyState == 4) {
    xhr.onreadystatechange = empty
    clearTimeout(abortTimeout)
    var result, error = false
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == "file:")) {
      dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader("content-type"))

      if (xhr.responseType == "arraybuffer" || xhr.responseType == "blob")
        result = xhr.response
      else {
        result = xhr.responseText

        try {
          // http://perfectionkills.com/global-eval-what-are-the-options/
          // sanitize response accordingly if data filter callback provided
          result = ajaxDataFilter(result, dataType, settings)
          if (dataType == "script")    (1,eval)(result)
          else if (dataType == "xml")  result = xhr.responseXML
          else if (dataType == "json") result = blankRE.test(result) ? null : $.parseJSON(result)
        } catch (e) { error = e }

        if (error) return ajaxError(error, "parsererror", xhr, settings, deferred)
      }

      ajaxSuccess(result, xhr, settings, deferred)
    } else {
      ajaxError(xhr.statusText || null, xhr.status ? "error" : "abort", xhr, settings, deferred)
    }
  }
}
readyState

readyState 有以下5種狀態(tài),狀態(tài)切換時,會響應(yīng) onreadystatechange 的回調(diào)。

0 xhr 實例已經(jīng)創(chuàng)建,但是還沒有調(diào)用 open 方法。
1 已經(jīng)調(diào)用 open 方法
2 請求已經(jīng)發(fā)送,可以獲取響應(yīng)頭和狀態(tài) status
3 下載中,部分響應(yīng)數(shù)據(jù)已經(jīng)可以使用
4 請求完成

具體見 MDN:XMLHttpRequest.readyState

清理工作
xhr.onreadystatechange = empty
clearTimeout(abortTimeout)

當(dāng) readyState 變?yōu)?4 時,表示請求完成(無論成功還是失?。?,這時需要將 onreadystatechange 重新賦值為 empty 函數(shù),清除超時響應(yīng)定時器,避免定時器超時的任務(wù)執(zhí)行。

成功狀態(tài)判斷
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == "file:")) {
          ...
   }

這里判斷的是 http 狀態(tài)碼,狀態(tài)碼的含義可以參考 HTTP response status codes。

解釋一下最后這個條件 xhr.status == 0 && protocol == "file:"。

status0 時,表示請求并沒有到達(dá)服務(wù)器,有幾種情況會造成 status0 的情況,例如網(wǎng)絡(luò)不通,不合法的跨域請求,防火墻攔截等。

直接用本地文件的方式打開,也會出現(xiàn) status0 的情況,但是我在 chrome 上測試,在這種情況下只能取到 status , responseTyperesponseText 都取不到,不清楚這個用本地文件打開時,進(jìn)入成功判斷的目的何在。

處理數(shù)據(jù)
blankRE = /^s*$/,

dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader("content-type"))
if (xhr.responseType == "arraybuffer" || xhr.responseType == "blob")
  result = xhr.response
else {
  result = xhr.responseText

  try {
    // http://perfectionkills.com/global-eval-what-are-the-options/
    // sanitize response accordingly if data filter callback provided
    result = ajaxDataFilter(result, dataType, settings)
    if (dataType == "script")    (1,eval)(result)
    else if (dataType == "xml")  result = xhr.responseXML
    else if (dataType == "json") result = blankRE.test(result) ? null : $.parseJSON(result)
  } catch (e) { error = e }
  if (error) return ajaxError(error, "parsererror", xhr, settings, deferred)

首先獲取 dataType,后面會根據(jù) dataType 來判斷獲得的數(shù)據(jù)類型,進(jìn)而調(diào)用不同的方法來處理。

如果數(shù)據(jù)為 arraybufferblob 對象時,即為二進(jìn)制數(shù)據(jù)時,resultresponse 中直接取得。

否則,用 responseText 獲取數(shù)據(jù),然后再對數(shù)據(jù)嘗試解釋。

在解釋數(shù)據(jù)前,調(diào)用 ajaxDataFilter 對數(shù)據(jù)進(jìn)行過濾。

如果數(shù)據(jù)類型為 script ,則使用 eval 方法,執(zhí)行返回的 script 內(nèi)容。

這里為什么用 (1, eval) ,而不是直接用 eval 呢,是為了確保 eval 執(zhí)行的作用域是在 window 下。具體參考:(1,eval)("this") vs eval("this") in JavaScript? 和 《Global eval. What are the options?》

如果 dataTypexml ,則調(diào)用responseXML 方法

如果為 json ,返回的內(nèi)容為空時,結(jié)果返回 null ,如果不為空,調(diào)用 $.parseJSON 方法,格式化為 json 格式。相關(guān)分析見《讀zepto源碼之工具函數(shù)》

如果解釋出錯了,則調(diào)用 ajaxError 方法,觸發(fā) ajaxError 事件,事件類型為 parseerror

如果都成功了,則調(diào)用 ajaxSuccess 方法,執(zhí)行成功回調(diào)。

響應(yīng)出錯
ajaxError(xhr.statusText || null, xhr.status ? "error" : "abort", xhr, settings, deferred)

如果 status 不在成功的范圍內(nèi),則調(diào)用 ajaxError 方法,觸發(fā) ajaxError 事件。

響應(yīng)超時
if (settings.timeout > 0) abortTimeout = setTimeout(function(){
  xhr.onreadystatechange = empty
  xhr.abort()
  ajaxError(null, "timeout", xhr, settings, deferred)
}, settings.timeout)

如果有設(shè)置超時時間,則設(shè)置一個定時器,超時時,首先要將 onreadystatechange 的回調(diào)設(shè)置為空函數(shù) empty ,避免超時響應(yīng)執(zhí)行完畢后,請求完成,再次執(zhí)行成功回調(diào)。

然后調(diào)用 xhr.abort 方法,取消請求的發(fā)送,并且調(diào)用 ajaxError 方法,觸發(fā) ajaxError 事件。

$.get
$.get = function(/* url, data, success, dataType */){
  return $.ajax(parseArguments.apply(null, arguments))
}

$.get$.ajax GET 請求的便捷方法,內(nèi)部調(diào)用了 $.ajax ,不需要指定請求類型。

$.post
$.post = function(/* url, data, success, dataType */){
  var options = parseArguments.apply(null, arguments)
  options.type = "POST"
  return $.ajax(options)
}

$.post$.ajax POST 請求的便捷方法,跟 $.get 一樣,只開放了 url、datasuccessdataType 等幾個接口參數(shù),默認(rèn)配置了 typePOST 請求。

$.getJSON
$.getJSON = function(/* url, data, success */){
  var options = parseArguments.apply(null, arguments)
  options.dataType = "json"
  return $.ajax(options)
}

$.getJSON$.get 差不多,比 $.get 更省了一個 dataType 的參數(shù),這里指定了 dataTypejson 類型。

$.fn.load
$.fn.load = function(url, data, success){
  if (!this.length) return this
  var self = this, parts = url.split(/s/), selector,
      options = parseArguments(url, data, success),
      callback = options.success
  if (parts.length > 1) options.url = parts[0], selector = parts[1]
  options.success = function(response){
    self.html(selector ?
              $("
").html(response.replace(rscript, "")).find(selector) : response) callback && callback.apply(self, arguments) } $.ajax(options) return this }

load 方法是用 ajax 的方式,請求一個 html 文件,并將請求的文件插入到頁面中。

url 可以指定選擇符,選擇符用空格分割,如果有指定選擇符,則只將匹配選擇符的文檔插入到頁面中。url 的格式為 請求地址 選擇符。

var self = this, parts = url.split(/s/), selector,
   options = parseArguments(url, data, success),
   callback = options.success
if (parts.length > 1) options.url = parts[0], selector = parts[1]

parts 是用空格分割后的結(jié)果,如果有選擇符,則 length 會大于 1,數(shù)組的第一項為請求地址,第二項為選擇符。

調(diào)用 parseArguments 用來重新調(diào)整參數(shù),因為 datasuccess 都是可選的。

options.success = function(response){
  self.html(selector ?
            $("
").html(response.replace(rscript, "")).find(selector) : response) callback && callback.apply(self, arguments) }

請求成功后,如果有 selector ,則從文檔中篩選符合的文檔插入頁面,否則,將返回的文檔全部插入頁面。

如果有配置回調(diào)函數(shù),則執(zhí)行回調(diào)。

系列文章

讀Zepto源碼之代碼結(jié)構(gòu)

讀 Zepto 源碼之內(nèi)部方法

讀Zepto源碼之工具函數(shù)

讀Zepto源碼之神奇的$

讀Zepto源碼之集合操作

讀Zepto源碼之集合元素查找

讀Zepto源碼之操作DOM

讀Zepto源碼之樣式操作

讀Zepto源碼之屬性操作

讀Zepto源碼之Event模塊

讀Zepto源碼之IE模塊

讀Zepto源碼之Callbacks模塊

讀Zepto源碼之Deferred模塊

參考

Zepto源碼分析-ajax模塊

讀zepto源碼(3) ajax

你真的會使用XMLHttpRequest嗎?

原來你是這樣的 jsonp(原理與具體實現(xiàn)細(xì)節(jié))

一個普通的 Zepto 源碼分析(二) - ajax 模塊

MDN:XMLHttpRequest

fetch.spec.whatwg.org

HTTP status code 0 - what does this mean for fetch, or XMLHttpRequest?

(1,eval)("this") vs eval("this") in JavaScript?

Global eval. What are the options?

License

最后,所有文章都會同步發(fā)送到微信公眾號上,歡迎關(guān)注,歡迎提意見:

作者:對角另一面

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84989.html

相關(guān)文章

  • Zepto源碼Stack模塊

    摘要:讀源碼系列文章已經(jīng)放到了上,歡迎源碼版本本文閱讀的源碼為改寫原有的方法模塊改寫了以上這些方法,這些方法在調(diào)用的時候,會為返回的結(jié)果添加的屬性,用來保存原來的集合。方法的分析可以看讀源碼之模塊。 Stack 模塊為 Zepto 添加了 addSelf 和 end 方法。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto 源碼版本 本文閱讀的...

    crossea 評論0 收藏0
  • Zepto源碼assets模塊

    摘要:模塊是為解決移動版加載圖片過大過多時崩潰的問題。因為沒有處理過這樣的場景,所以這部分的代碼解釋不會太多,為了說明這個問題,我翻譯了這篇文章作為附文怎樣處理移動端對圖片資源的限制,更詳細(xì)地解釋了這個模塊的應(yīng)用場景。 assets 模塊是為解決 Safari 移動版加載圖片過大過多時崩潰的問題。因為沒有處理過這樣的場景,所以這部分的代碼解釋不會太多,為了說明這個問題,我翻譯了《How to...

    thursday 評論0 收藏0
  • Zepto源碼Gesture模塊

    摘要:模塊基于上的事件的封裝,利用屬性,封裝出系列事件。這個判斷需要引入設(shè)備偵測模塊。然后是監(jiān)測事件,根據(jù)這三個事件,可以組合出和事件。其中變量對象和模塊中的對象的作用差不多,可以先看看讀源碼之模塊對模塊的分析。 Gesture 模塊基于 IOS 上的 Gesture 事件的封裝,利用 scale 屬性,封裝出 pinch 系列事件。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡...

    coolpail 評論0 收藏0
  • Zepto源碼Form模塊

    摘要:模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā)事件,提交表單。最終返回的結(jié)果是一個數(shù)組,每個數(shù)組項為包含和屬性的對象。否則手動綁定事件,如果沒有阻止瀏覽器的默認(rèn)事件,則在第一個表單上觸發(fā),提交表單。 Form 模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā) submit 事件,提交表單。 讀 Zepto 源碼系列文章已...

    陳江龍 評論0 收藏0
  • Zepto源碼fx_methods模塊

    摘要:所以模塊依賴于模塊,在引入前必須引入模塊。原有的方法分析見讀源碼之樣式操作方法首先調(diào)用原有的方法,將元素顯示出來,這是實現(xiàn)動畫的基本條件。如果沒有傳遞,或者為值,則表示不需要動畫,調(diào)用原有的方法即可。 fx 模塊提供了 animate 動畫方法,fx_methods 利用 animate 方法,提供一些常用的動畫方法。所以 fx_methods 模塊依賴于 fx 模塊,在引入 fx_m...

    junbaor 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<