1 引子
觀察者模式是我們日常開發中經常用的模式。這個模式由兩個主要部分組成:發布者和觀察者。通過觀察者模式,實現發布者和觀察者的解耦。
發布者主要負責發布內容,觀察者主要負責監聽發布者發布的內容,並作出相應的動作。和我們平時訂閱期刊一樣,cnki會維護一個訂閱者列表,有期刊被發布出來時,cnki會將這些期刊推送給訂閱者。從程序角度來說,訂閱者就是一堆的方法,發布者的推送內容的動作就是依次調用訂閱者列表中的方法(訂閱者),而發布的內容就將以參數的形式提供給訂閱者。
在jQuery中Deferred就是用來實現這一模式的。
我們首先來一個簡單的訂閱與發布示例:
//創建一個發布者
var subject=$.Deferred();
//創建兩個訂閱者
function subscriber1(content){
console.log(content);
}
function subscriber2(content){
console.log(content);
}
//訂閱內容
subject.done(subscriber1,subscriber2);
//發布內容
subject.resolve('謝謝你的訂閱');
下面我們來分析,$.Deferred的源碼,在這個過程中,我們還會$.Deferred的各種使用情況。
2 構建Deferred
首先我們將Deferred源碼做簡化處理:
其中,tuples是一種數據結構。它是一種非常巧妙的設計,是把有共性的代碼合並到一起,然后一次性進行處理。promise是deferred的一種簡化形式(去掉了改變狀態的接口)。Deferred是一個工廠類,返回的是內部創建好的deferred對象。
// 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對象:
在構建deferred對象:
構建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,^是按位運算符
沒什么大用,但是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對象:
至此,Deferred對象構建完成。
比較發現,promise比deferred少了6個內容推送功能。沒有了這幾個功能deferred的狀態也就不會被改變。
3 Deferred的使用
3.1 done,resolve; fail,reject; progress,notify
這個過程非常簡單,只是對Callbacks的簡單包裝。
這里的原理和是那個面的一模一樣。
3.2 then()
then方法返回一個新的的Deferred對象的精簡版promise。在創建該Deferred是傳遞進去了一個function參數。我們發現,在構建Deferred的工廠方法就有一個func參數,它內部的處理是這樣的:
以工廠方法創建的deferred為上下文,以該deferred為參數來調用這個方法。函數執行完后,工廠方法將deferred返回。
然后我們來看這個func具體是怎么設計的,這里的設計的非常之復雜。這里最重要的概念就是作用域鏈,如果對這些基礎知識不熟悉的話,理解起來真的很費勁……
以上面實例代碼為例,來分析then的源碼:
分析源碼不僅可以回顧JavaScript知識,還可以借鑒別人的設計思路,代碼書寫方式,對提高自身的修養是很有幫助的。
下一章分析jQuery.when的實現……