jQuery.Deferred 源碼分析


作者:禪樓望月( http://www.cnblogs.com/yaoyinglong

1 引子

觀察者模式是我們日常開發中經常用的模式。這個模式由兩個主要部分組成:發布者和觀察者。通過觀察者模式,實現發布者和觀察者的解耦。

發布者主要負責發布內容,觀察者主要負責監聽發布者發布的內容,並作出相應的動作。和我們平時訂閱期刊一樣,cnki會維護一個訂閱者列表,有期刊被發布出來時,cnki會將這些期刊推送給訂閱者。從程序角度來說,訂閱者就是一堆的方法,發布者的推送內容的動作就是依次調用訂閱者列表中的方法(訂閱者),而發布的內容就將以參數的形式提供給訂閱者。

image

在jQuery中Deferred就是用來實現這一模式的。

我們首先來一個簡單的訂閱與發布示例:

[+] view code

//創建一個發布者

var subject=$.Deferred();

//創建兩個訂閱者

function subscriber1(content){

    console.log(content);

}

function subscriber2(content){

    console.log(content);

}

//訂閱內容

subject.done(subscriber1,subscriber2);

//發布內容

subject.resolve('謝謝你的訂閱');

image

下面我們來分析,$.Deferred的源碼,在這個過程中,我們還會$.Deferred的各種使用情況。

2 構建Deferred

首先我們將Deferred源碼做簡化處理:

image

其中,tuples是一種數據結構。它是一種非常巧妙的設計,是把有共性的代碼合並到一起,然后一次性進行處理。promise是deferred的一種簡化形式(去掉了改變狀態的接口)。Deferred是一個工廠類,返回的是內部創建好的deferred對象。

[+] view code
var tuples =[
  // action, add listener, listener list, final state
  //once 表示resolve和reject只要觸發過一次,后面就不能再被觸發了
  ["resolve","done", jQuery.Callbacks("once memory"),"resolved"],
  ["reject","fail", jQuery.Callbacks("once memory"),"rejected"],
  ["notify","progress", jQuery.Callbacks("memory")]
]

tuples定義3中狀態的,即resolve,reject,notify。這三種狀態分別對應了3中訂閱接口(done,fail,progress)和3種訂閱列表(3個Callbacks),resolve和reject有最終的狀態(resolved和rejected)。下面就是為找這個數據結構,來構建帶這3中狀態的Deferred。

首先定義了初始狀態:state = "pending"

然后構建promise對象:

image

在構建deferred對象:

image

構建deferred分為2步:

1. 構建3種狀態對應的接口,訂閱者列表,和推送內容接口。

構建訂閱者列表,3430行,列表由一個Callbacks來管理,其實整個Deferred就是基於Callbacks來實現的,把它們的代碼放在一起對比,會發現出奇的相似,構建狀態字符串(分別為resolved、rejected或者undefined)。

$.each執行完會有3個列表生成:

doneList=$.Callbacks("once memory");
failList=$.Callbacks("once memory");
progressList=jQuery.Callbacks("memory");

定義訂閱接口3434行,他們就是訂閱列表(Callbacks)的add方法。add進去的訂閱者會存儲在Callbacks的list數組中。

3中訂閱接口分別為:

promise.done=doneList.add;
promise.fail=failList.add;
promise.progress=progressList.
add

為resolved和rejected兩種狀態的列表添加預設的3個回調函數(3437~3444)。第一個回調函數用於處理Deferred的狀態(已成功或已失敗),第二個回調函數用於讓另一個列表失效,這是因為當發布者發布內容的動作已經成功了,它就不可能再觸發失敗的狀態,因此讓失敗列表失效,防止給正在訂閱了失敗狀態的函數被執行。反之亦然。然后鎖定notify列表。鎖定notify只是讓其的progressList的fire方法(即后面不能使用notify和notifyWith再進行推送內容了)不能被調用了。但是,我們還可以給notify狀態繼續添加訂閱者,這些訂閱者會立即被執行。

這里有個小技巧:i^1,^是按位運算符

image

沒什么大用,但是jQuery就是要連這點性能也要節省出來。

至此,doneList的list中有3個回調函數[changeState, failList.disable, progressList.lock],failList的list中同樣有3個回調函數[changeState, doneList.disable, progressList.lock]

構建推送內容接口(3447~3451),三種接口名字分別為

deferred.resolve(成功),deferred.reject(失敗),deferred.notify(正在執行),這三個接口在內部其實又去調用這三個接口對應的帶參數版本(deferred.resolveWith,deferred.rejectWith,deferred.notifyWith),而三個接口又是訂閱列表(Callbacks)的fireWith方法。由此可見,要能非常明白的看懂Deferred的源碼,必須對Callbacks的源碼非常熟悉,至少要能熟練運用Callbacks。

2. 將deferred的精簡版promise中的方法和屬性或從到自己身上。(3455)這里的promise也是一種非常巧妙的處理。

構造結束后我們就會得到如下圖所示的Deferred對象:

image

至此,Deferred對象構建完成。

比較發現,promise比deferred少了6個內容推送功能。沒有了這幾個功能deferred的狀態也就不會被改變。

3 Deferred的使用

3.1 done,resolve; fail,reject; progress,notify

[+] view code
 
        

image

這個過程非常簡單,只是對Callbacks的簡單包裝。

 

[+] view code

這里的原理和是那個面的一模一樣。

 

[+] view code

3.2 then()

[+] view code

image

then方法返回一個新的的Deferred對象的精簡版promise。在創建該Deferred是傳遞進去了一個function參數。我們發現,在構建Deferred的工廠方法就有一個func參數,它內部的處理是這樣的:

image

以工廠方法創建的deferred為上下文,以該deferred為參數來調用這個方法。函數執行完后,工廠方法將deferred返回。

然后我們來看這個func具體是怎么設計的,這里的設計的非常之復雜。這里最重要的概念就是作用域鏈,如果對這些基礎知識不熟悉的話,理解起來真的很費勁……

image

以上面實例代碼為例,來分析then的源碼:

image

分析源碼不僅可以回顧JavaScript知識,還可以借鑒別人的設計思路,代碼書寫方式,對提高自身的修養是很有幫助的。

下一章分析jQuery.when的實現……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM