天天看點

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

一、前言                           

  jQuery.Deferred作為1.5的新特性出現在jQuery上,而jQuery.ajax函數也做了相應的調整。是以我們能如下的使用xhr請求調用,并實作事件處理函數晚綁定。

  我還一度以為這就是Promises/A+規範的實作,但其實jQuery.Deferred應該與jsDeferred歸為一類,我稱之為Before Promises/A。雖然jQuery.Deferred的出現會導緻初接觸Promise的朋友産生不少的誤解,但同時證明了Promises/A+規範的實作已成為開發過程中必不可少的利器了。

  接下來我們會踏上從1.5到2.1版本的jQuery.Deferred實作的剖析之旅,有興趣的朋友們請坐穩扶好哦!!!

  由于篇幅較長,特設目錄一坨!

  jQuery.Deferred 中主要包含三個對象類型Deferred、EnhancedDeferred和Promise,Deferred作為基礎類型用于建構更複雜的EnhancedDeferred類型,EnhancedDeferred執行個體則是使用者直接操作的對象,而Promise則是EnhancedDeferred的功能子集,僅提供成功/失敗回調函數的訂閱、關聯的EnhancedDeferred執行個體的狀态查詢功能。

  Deferred執行個體的狀态:initialized 、fired和cancelled。而狀态間的轉換關系如下:

     initialized -> fired      initalized -> cancelled

  EnhancedDeferred執行個體的狀态:initialized、resolved、rejected。而狀态間的轉換關系如下:

  initialized -> resolved   initialized -> rejected

(注意:上述類型和類型狀态均根據源碼分析得出,源碼中并沒有明确注明)

  1.5的jQuery.Deferred實作位于core.js檔案中的,下面我将相關代碼抽取并分組來分析。

  1. Deferred執行個體工廠

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

    Deferred執行個體内部維護着名為callbacks的回調函數隊列(而不是Promises/A+規範中的成功/失敗事件處理函數和Deferred單向連結清單)。然後将目光移到done方法,透過其實作可知jQuery.Deferred是支援回調函數晚綁定的(jsDeferred不支援,Promises/A+規範支援),但均以resovleWith的參數作為回調函數的入參,而不是上一個回調函數的傳回值作為下一個回調函數的入參來處理,無法形成責任鍊模式(Promises/A+規範支援)。

  2. 對外API——jQuery.Deferred

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

   jQuery.Deferred函數傳回一個EnhancedDeferred執行個體,而EnhancedDeferred是以一個管理成功回調函數隊列的Deferred執行個體為基礎,并将另一個用于管理失敗回調函數隊列的Deferred執行個體作為EnhancedDeferred執行個體擴充功能的實作提供者,很明顯成功、失敗回調函數隊列是獨立管理和執行。

  3. 輔助方法——jQuery.when

    功能就是等待所有入參均傳回值後,以這些傳回值為入參調用回調隊列的函數

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

  jQuery.Deferred中的Deferred執行個體和EnhancedDeferred執行個體均設計了隐式的狀态辨別,是以支援回調函數晚綁定的功能,但由于其采用兩個Deferred執行個體分類管理所有成功/失敗回調函數,而不是采用Deferred執行個體單向連結清單的結構,是以無法實作成功和失敗回調函數之間的資料傳遞,并且沒有對回調函數的抛異常的情況作處理。并且resolveWith的周遊調用回調函數隊列中沒有采用責任鍊模式,與Promises/A+規範截然不同。另外回調函數均為同步調用,而不是Promises/A+中的異步調用。是以我們隻能将其列入Before Promises/A的隊列中了!

  jQuery1.5除了新增jQuery.Deferred特性,還以jQuery.Deferred為基礎對ajax子產品進行增強,相關代碼如下:

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

  可能是jQuery的開發團隊意識到jQuery.Deferred的實作與Promises/A+規範相距甚遠,于是在1.6版本上更新檔式地為EnhancedDeferred增加了一個 pipe方法 ,進而實作回調函數的責任鍊。另外jQueyr.Deferred已經成為一個獨立的子產品deferred.js了(《JavaScript架構設計》中的示例就是1.6的)。

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

  除了pipe函數外,1.6還為EnhancedDeferred執行個體新增了 always函數 ,通過它添加的回調函數,無論EnhancedDeferred執行個體狀态為"resolved"還是"rejected"均會被執行。

   另外1.6對$.when進行了重構使代碼更容易了解。并且effectes和queue子產品可以開始以jQuery.Deferred作為基礎提供then方法等API了。

   由于VS2012建立Asp.Net項目時預設自帶jQuery1.7,我想Asp.Net的攻城獅們對它應該不陌生了。而1.7版本的jQuery.Deferred相對于以前的版本新增了 progress 、 notify 和 notifyWith 的API,但到底有什麼用呢?1.7版本的jQuery.Deferred是否更接近Promises/A+規範呢?答案是否定的。

   新版的jQuery.Deferred内部新增一個回調函數隊列,該隊列不像1.6版本中的deferred和failDeferred那樣隻能觸發一次"initialized"->"fired"的狀态轉換,而是可以進行多次并且與deferred和failDeferred一樣支援回調函數晚綁定。而 progress 、 notify 和 notifyWith 則與這個新的回調函數隊列相關。

   另外1.7版本中對jQuery.Deferred進行全局重構,不再由原來的 $._Deferred 來建構Deferred執行個體,而是通過 jQuery.Callbacks函數 來生成回調函數隊列管理器來代替(作用是一樣的,但回調函數隊列管理器更具有通用性),而上文提到的EnhancedDeferred則由三個回調函數隊列管理器組成。

   在陷入源碼前再次強調一點——1.7與1.6版本在本質上是一點都沒變!!

   1. 首先我們一起來看看重構的重心—— jQuery.Callbacks函數 (位于callbacks.js檔案中)

       作用:建立回調函數隊列管理器執行個體。

       回調函數隊列管理器存在以下狀态:

    initialized: 管理器執行個體初始狀态;

     firing: 正在周遊回調函數隊列并按FIFO順序調用回調函數;

     fired: 周遊完回調函數隊列,等待接受下一次周遊請求;

     locked: 鎖定管理器,無法再接受周遊回調函數的請求;

     dying: 管理器進入臨死狀态,隻要此時狀态轉換為fired或locked,則會直接跳轉為disabled狀态;

     disabled: 管理器将被廢棄,無法再使用了。

      狀态間的轉換關系如下:

  ①. initialized -> firing <-> fired [-> disabled|locked]   ②. initialized <-> firing <-> fired [-> disabled|locked]   ③. initialized -> locked -> disabled   ④. initialized -> dying -> locked -> disabled   ⑤. initialized -> dying -> fired -> disabled   ⑥. initialized -> dying -> fired -> firing

      在調用jQuery.Callbacks時可以通過可選入參來配置管理器的一些特性,分别為:

        unique,是否確定隊列中的回調函數的唯一性。

        stopOnFalse,是否當某個回調函數傳回值為false時,将配置管理器的狀态設定為dying。

        once,是否僅能執行一次隊列周遊操作。若不限制僅能執行一次隊列周遊(預設值),則狀态轉換關系為②、③和⑥。

        memory,是否支援函數晚綁定。若不支援晚綁定且僅能執行一次隊列周遊操作,則狀态轉換關系為③、④和⑤。若支援晚綁定則為①和③。

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

   2. 然後就是jQuery.Deferred的改造

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

  1.7中通過 私有屬性state 明确辨別Deferred執行個體的狀态(pending、resolved和rejected),但可惜的是這些屬性對Deferred執行個體的行為沒有任何作用,感覺有沒有這些狀态都沒有所謂。

  經過這樣一改,就更明确Deferred執行個體其實對三個回調函數隊列的統一管理入口而已了。

  jQuery1.8的jQuery.Deferred依然依靠jQuery.Callbacks函數生成的三個回調函數隊列管理器作為Deferred的建構基礎,該版本大部分均為對jQuery.Deferred和jQuery.Callbacks代碼結構、語義層面的局部重構,使得更容易了解和維護,尤其是對jQuery.Callbacks代碼重構後,回調函數隊列管理器執行個體的狀态關系轉換清晰不少。

  而比較大的局部功能重構是jQuery.Deferred的then方法被重構成為pipe方法的别名,而pipe函數的實作為Promise/A規範中的then方法,是以1.8的then方法與舊版本的then方法不完全相容。

  jQuery1.9和2.1并沒重構或為jQuery.Deferred添加新功能,可以直接跳過。

  通過上述内容大家已經清楚jQuery.Deferred并不是Promise/A+規範的完整實作(甚至可以說是相距甚遠),且jQuery1.8中then函數的實作方式與舊版本的不同,埋下了相容陷阱,但由于jQuery.Deferred閱聽人面少(直接使用Ajax、effects和queue子產品的Promise形式的API較多),是以影響範圍不大,慶幸慶幸啊!

  《JavaScript架構設計》

如果您覺得本文的内容有趣就掃一下吧!捐贈互勉!

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

<a href="http://home.cnblogs.com/u/fsjohnhuang/">^_^肥仔John</a>

<a href="http://home.cnblogs.com/u/fsjohnhuang/followees">關注 - 85</a>

<a href="http://home.cnblogs.com/u/fsjohnhuang/followers">粉絲 - 707</a>

<a>+加關注</a>

<a></a>

<a href="http://www.ucancode.com/index.htm" target="_blank">【推薦】超50萬VC++源碼: 大型工控、組态\仿真、模組化CAD源碼2018!</a>

<a href="https://cloud.tencent.com/developer/support-plan?fromSource=gwzcw.710852.710852.710852" target="_blank">【推薦】加入騰訊雲自媒體扶持計劃,免費領取域名&amp;伺服器</a>

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

<b>最新IT新聞</b>:

JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析

<b>最新知識庫文章</b>:

<a href="https://github.com/fsjohnhuang" target="_blank">肥仔John@github</a>

作品:

<a href="https://github.com/fsjohnhuang/iScheme" target="_blank">iScheme—Scheme解釋器</a>

<a href="https://github.com/fsjohnhuang/preview" target="_blank">preview.js—純前端的圖檔預覽元件</a>

繼續閱讀