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

来源:http://www.cnblogs.com/BlackStorm/archive/2017/08/13/Zepto-Analysing-For-Ajax-Module.html
-Advertisement-
Play Games

普通的路人,普通地瞧。簡單粗暴地分析了 Zepto 的 Ajax 模塊,分析時使用的是目前最新 1.2.0 版本。 ...


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

普通的路人,普通地瞧。分析時使用的是目前最新 1.2.0 版本。

Zepto 可以由許多模塊組成,預設包含的模塊有 zepto 核心模塊,以及 event 、 ajax 、 form 、 ie ,其中 ajax 模塊是比較重要的模塊之一,我們可以藉助它提供的方法去做一些網路請求,還可以監聽它的生命周期事件。

Zepto 基本模塊之 ajax 模塊

我們都已經知道 Zepto 插件的一般形式是把 Zepto 對象傳入給 $ 形參,那麼可以先搜索 $. 開頭的代碼段,從暴露的函數入手來分析整個代碼結構。

代碼結構與分析

刪減出外形:

;(function($){
  // Number of active Ajax requests
  $.active = 0

  $.ajaxJSONP = function(options, deferred){...}

  $.ajaxSettings = {...}

  $.ajax = function(options){...}

  $.get = function(/* url, data, success, dataType */){...}

  $.post = function(/* url, data, success, dataType */){...}

  $.getJSON = function(/* url, data, success */){...}

  $.fn.load = function(url, data, success){...}

  $.param = function(obj, traditional){...}
})(Zepto)

另由靜態分析可知:

  1. $.get()$.post()$.getJSON()$.fn.load() 均調用了 $.ajax()parseArguments()說明 $.ajax() 才是我們主要分析的目標,後者則是處理函數參數的關鍵;
  2. $.param()$.ajax()$.ajaxJSONP() 均調用了 $.isFunction() ,這個倒是沒有什麼好糾結的,就是用了 Zepto 核心定義的一個判斷傳入參數是否為函數的函數;
  3. $.ajax() 操作、返回的是一個原生的 xhr 對象,調用了很多 ajax 開頭的內部函數來完成生命周期的控制封裝。

參數規格化與 MIME

先來看看 parseArguments() 都幹了些什麼:

  // handle optional data/success arguments
  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
    }
  }

它的參數覆蓋了我們之前提到的四個調用者的參數。

在前兩行我們可以看到,它做了一個順移來完成對重載調用格式的支持。比如 $.get(url, function(data, status, xhr){ ... }) 。這個是簡單判斷參數是否為函數來完成的,有兩個缺點,一是會重覆判斷 success ,二是當只傳兩個參數時會做冗餘賦值。

那麼這個函數的作用就是參數規格化。然而.. 在 Zepto 文檔上並沒有看到對 dataType 的說明,略坑?

我們已知 $.ajaxSettings 里有一個 accepts 屬性,文檔上說是根據 dataType 來請求伺服器的,而代碼註釋里則說這是一個 Mapping ;另外根據對 $.ajax() 的靜態分析,我們還有一個 mimeToDataType() ,它根據輸入的 MIME 字元串來輸出內部定義的 dataType :

  var scriptTypeRE = /^(?:text|application)\/javascript/i,
      xmlTypeRE = /^(?:text|application)\/xml/i,
      jsonType = 'application/json',
      htmlType = 'text/html'

  $.ajaxSettings = {
    // 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'
    }
  }

  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'
  }

其中 mime.split(';', 2) 限定了只能用一個分號分成兩部分,但我質疑它的效果.. 顯然限定為 1 是更好的。

get 與 post

接下來就可以來看 get/post 方法了:

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

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

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

嗯,沒什麼好分析的, apply 也是很常見的用法。但是我們確定之前是沒有 type 屬性的,那麼可以猜測 $.ajax() 還會對 options 作進一步處理,比如合併 $.ajaxSettings 中的設置等等。

load() 函數

這是掛到原型上的,我們已知 Zepto 調用原型函數前都會把自己弄成一個類數組,也就是自己定義的集合 Collection 。

文檔上說這個方法可以給一個集合的元素用 GET Ajax 載入給定 URL 的 HTML 內容,還可以同時指定一個 CSS 選擇器,使其只載入符合這個選擇器的內容。而指定了選擇器以後,載入內容中的 script 則不會被執行。來看看是怎麼做的:

  $.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 ?
        $('<div>').html(response.replace(rscript, "")).find(selector)
        : response)
      callback && callback.apply(self, arguments)
    }
    $.ajax(options)
    return this
  }

這個 callback 操作好像挺迷的,前面多傳了 success 進去,多做了一次賦值。

同樣也沒多少好分析的,就是給 Ajax 添加了一個成功回調,用來設置元素的內容,並代理了傳入的回調。至於 .find() 是跟 jQuery 一樣的實現,當在一個集合上調用時,就篩出元素。

param() 函數

這個函數的扇入扇出也是比較少的,可以先分析。那麼這個方法也是一個序列化函數,可以把一個(狹義的)對象序列化成編碼 URL 字元串,當然也可以接收一個數組,但只接收 serializeArray 格式的。

  var escape = encodeURIComponent

  function serialize(params, obj, traditional, scope){
    var type, array = $.isArray(obj), hash = $.isPlainObject(obj)
    $.each(obj, function(key, value) {
      type = $.type(value)
      // 關註點 4 (遞歸進來)
      if (scope) key = traditional ? scope :
        scope + '[' + (hash || type == 'object' || type == 'array' ? key : '') + ']'
      // 關註點 2 (初始入口)
      // handle data in serializeArray() format
      if (!scope && array) params.add(value.name, value.value)
      // 關註點 3
      // recurse into nested objects
      else if (type == "array" || (!traditional && type == "object"))
        serialize(params, value, traditional, key)
      else params.add(key, value)
    })
  }

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

這次就不從 serialize() 開始看了,當然是從短的開始看啦!我說的短,不是代碼有多少行,而是除去賦值操作後,把字面量壓成一行後等等,還能剩下多短的結構。

那麼我們可以看到 $.param() 里做了一個臨時數組 params 用於存放每個鍵值對的序列化結果,最後 join 到一起做修補替換。關鍵點在 add() 函數,如果 value 是一個函數,則調用並獲得其返回值。但是.. 如果其返回值或者本來就是 nullundefined 應該返回空值嗎?這點我不敢苟同,就算不會解析錯誤,空鍵不如乾脆不要。至於改寫 escape 看起來也沒什麼必要.. 最多就是提醒寫插件的開發者.. 直接調用就好了哇..

接下來就是看起來很長的 serialize() 函數啦。初步目測是一個遞歸,用於處理嵌套情況。那麼三個局部變數一個是拿到小寫的常見類型名,後兩個是布爾值,相信不陌生。文件內搜索發現只有來自 $.param() 的引用,那麼可以斷定第二個 if 才是初始入口,這裡是處理 serializeArray 的鍵值對象格式。而如果是普通對象 k-v 對的值是數組或對象的話,就進入遞歸調用把子結構也序列化,否則直接把 k-v 對加入 params 數組中。

要註意的是,如果設置為傳統的淺序列化模式,嵌套對象值會被無情拋棄成 [object Object] 也就是 %5Bobject+Object%5D 。而數組的 key 則是不帶方括弧的表示形式,在 Zepto 上是無論嵌套多少層數組,都會處理成同 key 而不同 value 的多個鍵值對,但 jQuery 更新了其實現,它是無論嵌套多少層放在同一個鍵值對中,用英文逗號隔開,如下:

decodeURIComponent($.param({a:1,b:[1,[2,22,[3,33,[4]]],5]},true))
// jQuery, "a=1&b=1&b=2,22,3,33,4&b=5"
// Zepto,  "a=1&b=1&b=2&b=22&b=3&b=33&b=4&b=5"

至於帶方括弧的非傳統模式實現也比較簡單,每次遞歸更新 key 就好了。

Ajax 生命周期及事件

一共 7 個,都可以在官方文檔找到說明的。其中 ajaxStartajaxStop 事件只有設置為 global: true 才會在 document 上被激發,其餘則都是全局事件,在 document 或指定 DOM 節點上激發並冒泡。至於怎麼捕獲事件,相信熟悉的人都不陌生(好像是廢話)

  // trigger a custom event and return false if it was cancelled
  function triggerAndReturn(context, eventName, data) {
    var event = $.Event(eventName)
    $(context).trigger(event, data)
    return !event.isDefaultPrevented()
  }

  // trigger an Ajax "global" event
  function triggerGlobal(settings, context, eventName, data) {
    if (settings.global) return triggerAndReturn(context || document, eventName, data)
  }
  // 關註點 1
  // Number of active Ajax requests
  $.active = 0

  function ajaxStart(settings) {
    // 關註點 2
    if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
  }
  function ajaxStop(settings) {
    // 關註點 2
    if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop')
  }

維護了一個 active 變數,在第一次發起 Ajax 或最後一次結束中被檢查為 0 而觸發事件,若事件沒有被抑制則開始冒泡。沒有用設計模式,應該也沒必要。

  // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
  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])
  }
  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)
  }
  // type: "timeout", "error", "abort", "parsererror"
  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)
  }
  // status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
  function ajaxComplete(status, xhr, settings) {
    var context = settings.context
    settings.complete.call(context, xhr, status)
    triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings])
    ajaxStop(settings)
  }

剩下的 5 個事件在 4 個生命時期被激發。可以看到在 ajaxBeforeSend 里允許回調或事件被抑制,這時就會返回 false 進一步取消該 Ajax 。否則就觸發 ajaxSend 事件了——不過顯然,這個時候其實還沒有真正地 send 出去,只是先激活了事件。同時我們也能看到,無論 Ajax 請求成功還是失敗,最終都觸發完成事件,最後“標誌性”地終止——當它是最後一個 Ajax 時就會觸發 ajaxStop 事件。

此外我們還可以知道, Ajax 回調是先於事件發生的;而如果是 Promise ,那麼只有當 ajaxError 時才會 reject 。

$.ajax() 函數分析

終於到了重頭戲了。至於剩餘的其他邊角函數可以一眼掃光,用到再說吧~

其實大部分代碼都用來處理 settings 了,然而還是可以大致分為幾部分的。

配置項的合併

  $.ajax = function(options){
    var settings = $.extend({}, options || {}),
        deferred = $.Deferred && $.Deferred(),
        urlAnchor, hashIndex
    for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
    
    ajaxStart(settings)
    ...
  }

首先是淺複製 options 參數,接著繼承 $.ajaxSettings 中的屬性。同樣是淺複製,後者不能覆蓋用戶傳入的 options 參數.. 不過..

    var settings = $.extend({}, $.ajaxSettings)
    settings = $.extend(settings, options || {})

總感覺這是一樣的,哈哈。畢竟 for...in 能檢出原型上的屬性,而反正 $.extend() 淺複製時內部實現也是純 for...in ,好像沒毛病。要是支持 ES5 的話直接 Object.create() 好像也.. 沒毛病?

完成了設置項的初始化後,激發 ajaxStart 事件,開始做進一步的處理..

配置項的處理

  var originAnchor = document.createElement('a')
  // 關註點 1
  originAnchor.href = window.location.href

  $.ajax = function(options){
    ...
    if (!settings.crossDomain) {
      // 關註點 3
      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
      // 關註點 2 (自動處理出的 protocol 和 host)
      settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host)
    }
    ...
  }

對跨域屬性的處理。這裡有個特殊技巧,就是給 a 標簽修改 href 屬性後,瀏覽器會幫我們自動處理出 protocolhost 屬性,這對判斷是否跨域很有用,且不用調用冗長的解析庫。

我們知道不跨域的標準是協議相同、主機地址/功能變數名稱相同、埠號相同,而有人發現在 IE 且 80 埠下需要賦值完整地址才會把 host 解析出來,於是多了一個自賦值的 PR 。

  function appendQuery(url, query) {
    if (query == '') return url
    // 關註點 3
    return (url + '&' + query).replace(/[&?]{1,2}/, '?')
  }

  // serialize payload and append it to the URL for GET requests
  function serializeData(options) {
    // 關註點 4 (序列化 data 對象)
    if (options.processData && options.data && $.type(options.data) != "string")
      options.data = $.param(options.data, options.traditional)
    // 關註點 5 (預設 GET 的 url 處理)
    if (options.data && (!options.type || options.type.toUpperCase() == 'GET' || 'jsonp' == options.dataType))
      options.url = appendQuery(options.url, options.data), options.data = undefined
  }

  $.ajax = function(options){
    ...
    // 關註點 1
    if (!settings.url) settings.url = window.location.toString()
    // 關註點 2
    if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex)
    serializeData(settings)
    ...
  }

url 屬性的處理。 window.location 的使用是 DOM 基礎知識了,前面沒用我猜是為了保持一致性?(逃

這裡暫時不知道不保留 # 號後部分的作用,只知道 WHATWG 定義其為 URL-fragment ,沒有很特別的說明,也許有時間要看看 Node 的解析說明。

搞定了 url 後就可以序列化數據了。根據調用關係, appendQuery() 有 4 個扇入,唯一亮點就是每次把第一個出現的 & 或 ? 替換成 ? 。我認為這個實現是基於傳入 url 是 / 結尾的假設的,那麼其實判斷最後一個字元來決定使用 & 或 ? 應當比查找要好很多。至於 serializeData 就是兩種情況,如果提供的 options.data 不是一個字元串且需要自動序列化,那麼就調用之前提到的 $.param() 進行序列化,否則如果是 jsonp 或者預設 GET 則處理進 options.url 里。

    // 關註點 1
    var dataType = settings.dataType, hasPlaceholder = /\?.+=\?/.test(settings.url)
    if (hasPlaceholder) dataType = 'jsonp'
    // 關註點 2 (要不要緩存的判斷與處理)
    if (settings.cache === false || (
         (!options || options.cache !== true) &&
         ('script' == dataType || 'jsonp' == dataType)
        ))
      settings.url = appendQuery(settings.url, '_=' + Date.now())
    // 關註點 3 ( 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 的處理,也算一小段吧。不使用緩存的處理好理解,就是常見的加入時間參數。

hasPlaceholder 則是測試(貪婪匹配)最後一個鍵值對的值(即 url 中的 callbackName )是否為 placeholder 即 ? 符。這個實現很奇怪,已經不符合現在的 jQuery 了,現在似乎是不能只在 url 指定 =? 的,必須設置 dataType: jsonp 才行。另外先補 url 再替換似乎也有些低效。

原生 xhr 對象的 header 設置

首先是對 request header 的設置:

    var mime = settings.accepts[dataType],
        headers = { }, /* 關註點 1 (暫存 header ) */
        setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value] },
        xhr = settings.xhr(),
        nativeSetHeader = xhr.setRequestHeader

    if (deferred) deferred.promise(xhr)

    if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest')
    // 關註點 2
    setHeader('Accept', mime || '*/*')
    // 關註點 3 (註意優先順序)
    if (mime = settings.mimeType || mime) {
      if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
      xhr.overrideMimeType && xhr.overrideMimeType(mime)
    }
    //關註點 4
    if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET'))
      setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded')
    //關註點 4
    if (settings.headers) for (name in settings.headers) setHeader(name, settings.headers[name])
    xhr.setRequestHeader = setHeader

註意到在 v1.1.1 的 commit 記錄里寫到,為了支持在 beforeSend 生命期中(這個時候 xhr 對象還沒 open )調用 xhr.setRequestHeader() 修改 header ,用自定義的 setHeader() 函數暫存下來,到實際要操作打開 xhr 對象時再去調用原生方法設置 header 。

註意到這一句 setHeader('Accept', mime || '*/*') , MDN 上是這麼說的:

If no Accept header has been set using this, an Accept header with the */* is sent with the request when send() is called.

因此我認為可以改成 mime && setHeader('Accept', mime)

而緊接著的 if 具有一定的迷惑性,它其實是要用 accepts[dataType] 或者 mimeType 來重寫響應頭裡的 MIME (賦值的優先順序較低,其實完全可以拿出來賦值)。再下來就是針對非 GET 而又有上傳數據的請求,將 Content-Type 改為 POST 格式。再下來就是存下自定義的 header 並重寫方法了,可以看到自定義 header 會覆蓋 Zepto 的預設值。

發送 xhr

  $.ajax = function(options){
    ...
    xhr.onreadystatechange = function(){...}
    // 關註點 1
    if (ajaxBeforeSend(xhr, settings) === false) {
      xhr.abort()
      ajaxError(null, 'abort', xhr, settings, deferred)
      return xhr
    }
    // 關註點 2 (打開 xhr 對象)
    var async = 'async' in settings ? settings.async : true
    xhr.open(settings.type, settings.url, async, settings.username, settings.password)

    if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name]

    for (name in headers) nativeSetHeader.apply(xhr, headers[name])
    // 關註點 3 (超時的後續處理)
    if (settings.timeout > 0) abortTimeout = setTimeout(function(){
        xhr.onreadystatechange = empty
        xhr.abort()
        ajaxError(null, 'timeout', xhr, settings, deferred)
      }, settings.timeout)
    // 關註點 4
    // avoid sending empty string (#319)
    xhr.send(settings.data ? settings.data : null)
    return xhr
  }

先不管 onreadystatechange 回調,裡面只有一個完成狀態的判斷。

這裡終於跑到了第二個生命期,準備工作已經做好,觸發可以被取消的 ajaxBeforeSend 事件,接著就是打開 xhr 了。這裡有一個點是超時的處理,把 onreadystatechange 回調設置為空我認為是一個收尾工作,比如 $.ajax() 返回的 xhr 對象也可以重新打開,這時候顯然不希望還是原來的回調。另外不使用原生超時事件的原因應該是 Android 4.4 的瀏覽器還不支持。

最後 xhr.send() 註釋了對 #319 的修補。這個 issue 的大意是當在 Chrome 上 POST 的數據為空字元串時(經過上面的處理,傳入的 data 變為了 undefined ),會觸發一個 CORS 錯誤。應該是 11 年 Chrome 上的 BUG ,現在我無法復現了。

onreadystatechange() 回調

;(function($){
  var blankRE = /^\s*$/
  ...
    xhr.onreadystatechange = function(){
      if (xhr.readyState == 4) {
        xhr.onreadystatechange = empty
        clearTimeout(abortTimeout)
        var result, error = false
        // 關註點 1 (正常狀態碼的判斷)
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
          dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type'))
          // 關註點 2 (響應數據流)
          if (xhr.responseType == 'arraybuffer' || xhr.responseType == 'blob')
            result = xhr.response
          else {
            result = xhr.responseText
            // 關註點 3 ( evel() 的間接調用與數據類型判斷)
            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)
        }
      }
    }
  ...
}

對於本地文件(即 file: 協議頭)瀏覽器的狀態碼會是 0 。而如果是瀏覽器取消了 xhr 請求則觸發 abort 類型的 ajaxError 事件,如宿主機的網路連接變化 / 中斷了等。

有意思的點是 responseType 屬性。兩種類型我都沒見過,據 MDN 是用於二進位數據傳輸, 由 .response 返回相應的對象。

一個奇怪的技巧是如代碼註釋所示,使用間接調用的形式 (1,eval)() 來避免污染外層作用域。再吐個槽,對 dataType 的賦值可以放進 try 塊里的。

$.ajaxJSONP() 函數

接下來看下最後一個函數和 $.ajax() 相比有哪些不同。在文檔上標為廢棄,實際上是不建議直接使用。而在上面的代碼我們也看到 $.ajaxJSONP()$.ajax() 中的調用是發生在事件 ajaxStart 事件之後、配置項合併完成後、設置 header 之前的。

;(function($){
  var jsonpID = +new Date()
  ...

  $.ajaxJSONP = function(options, deferred){
    if (!('type' in options)) return $.ajax(options)
    // 關註點 2 (回調函數名的處理)
    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')
      }, /* 關註點 1 (只有取消方法的 xhr 對象) */
      xhr = { abort: abort }, abortTimeout

    if (deferred) deferred.promise(xhr)

    $(script).on('load error', function(e, errorType){...})

    if (ajaxBeforeSend(xhr, options) === false) {
      abort('abort')
      return xhr
    }
    // 關註點 3 (對自動回調的代理包裝,先拿到數據)
    window[callbackName] = function(){
      responseData = arguments
    }
    // 關註點 4 (最後一個 xxx=? 的替換)
    script.src = options.
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天想在Sublime Text(簡稱ST)內編寫HTML後直接使用瀏覽器看效果,想添加View in Browser插件,然後遇到奇怪的問題添加插件直接報"找不到有用的插件" 一開始懷疑是中文破解版的,換成原版的就會好。但是官網上下載原版還是不行,然後上網搜了一下,發現好多人遇到此問題。 接著就一 ...
  • 之前我的旅游網站前端部分只用了HTML+CSS解決,接下來逐步使用更高級的CSS特性和新引入的JavaScript技術來提高網站的交互體驗。 每到進行線上更新的時候,就來記錄一次更新的內容。 ...
  • 介紹常見的排序演算法:冒泡排序、選擇排序、插入排序、歸併排序、快速排序及簡單的性能測試。 ...
  • http://www.jeasyui.com/download/list.php 下載版本1.5.2的easyui中文API,可在CSDN網站http://download.csdn.net/download/tkk_lcm/9932078下載,不過需要有CSND賬號和1個積分才行。 ...
  • 在Reina(視網膜)屏幕的手機上,使用CSS設置的1px的邊框實際會比視覺稿粗很多。在之前的項目中,UI告訴我說我們移動項目中的邊框全部都變粗了,UI把他的設計稿跟我的屏幕截圖跟我看,居然真的不一樣。沒有辦法,只有在後面的版本中去修改了,但是要改的話,需要知道是為什麼。所以查了很多資料,終於搞懂了... ...
  • 超多經典 canvas 實例 普及:<canvas> 元素用於在網頁上繪製圖形。這是一個圖形容器,您可以控制其每一像素,必須使用腳本來繪製圖形。 註意:IE 8 以及更早的版本不支持 <canvas> 元素。 貼士:全部例子都分享在我的 GayHub - https://github.com/bxm ...
  • 一、Flex的簡介 Flex 是 Flexible Box 的縮寫,意為"彈性佈局",用來為盒狀模型提供最大的靈活性。用六個字概括彈性佈局就是簡單、方便、快速。 flex( flexible box:彈性佈局盒模型),是2009年w3c提出的一種可以簡潔、快速彈性佈局的屬性。主要思想是給予容器控制內 ...
  • 在前端項目開發中,px,em,以及rem都是頁面佈局常用的單位,雖然它們是長度單位,但是所含的意義不一樣。通過複習和查閱,總結了以下知識。 px像素(Pixel) 定義:相對長度單位。像素px是相對於顯示器屏幕解析度而言的。(引自CSS2.0手冊) 特點: 1:px代表的是像素,用它設置字體大小時, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...