jQuery 2.0.3 源碼分析 Deferred(最細的實現剖析,帶圖)
Deferred的概念請看第一篇
http://www.cnblogs.com/aaronjs/p/3348569.html
******************構建Deferred對象時候的流程圖**************************
**********************源碼解析**********************
因爲callback被剝離出去後,整個deferred就顯得非常的精簡
jQuery.extend({ Deferred : function(){} when : function() )}
對於extend的繼承這個東東,在之前就提及過jquery如何處理內部jquery與init相互引用this的問題
對於JQ的整體架構一定要弄懂 http://www.cnblogs.com/aaronjs/p/3278578.html
所以當jQuery.extend只有一個參數的時候,其實就是對jQuery靜態方法的一個擴展
我們在具體看看2個靜態方法內部都幹了些什麼:
Deferred整體結構:
源碼精簡了部分代碼
Deferred: function( func ) { var tuples = [ // action, add listener, listener list, final state [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], state = "pending", promise = { state: function() {}, always: function() {}, then: function( /* fnDone, fnFail, fnProgress */ ) { }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) {} }, deferred = {}; jQuery.each( tuples, function( i, tuple ) { deferred[ tuple[0] + "With" ] = list.fireWith; }); promise.promise( deferred ); // All done! return deferred; },
- 顯而易見Deferred是個工廠類,返回的是內部構建的deferred對象
- tuples 創建三個$.Callbacks對象,分別表示成功,失敗,處理中三種狀態
- 創建了一個promise對象,具有state、always、then、primise方法
- 擴展primise對象生成最終的Deferred對象,返回該對象
這裏其實就是3個處理,但是有個優化代碼的地方,就是把共性的代碼給抽象出來,通過動態生成了
具體源碼分析:
Deferred自身則圍繞這三個對象進行更高層次的抽象
- 觸發回調函數列表執行(函數名)
- 添加回調函數(函數名)
- 回調函數列表(jQuery.Callbacks對象)
- deferred最終狀態(第三組數據除外)
var tuples = [ // action, add listener, listener list, final state [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ],
這裏抽象出2組陣營:
1組:回調方法/事件訂閱
2組:通知方法/事件發佈
tuples 元素集 其實是把相同有共同特性的代碼的給合併成一種結構,然後通過一次處理
jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], stateString = tuple[ 3 ]; promise[ tuple[1] ] = list.add; if ( stateString ) { list.add(function() { state = stateString; // [ reject_list | resolve_list ].disable; progress_list.lock }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); } deferred[ tuple[0] ] = function() { deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); return this; }; deferred[ tuple[0] + "With" ] = list.fireWith; });
對於tuples的3條數據集是分2部分處理的
第一部分將回調函數存入
promise[ tuple[1] ] = list.add;
其實就是給promise賦予3個回調函數
promise.done = $.Callbacks("once memory").add
promise.fail = $.Callbacks("once memory").add
promise.progressl = $.Callbacks("memory").add
如果存在deferred最終狀態
默認會預先向doneList,failList中的list添加三個回調函數
if ( stateString ) { list.add(function() { // state = [ resolved | rejected ] state = stateString; // [ reject_list | resolve_list ].disable; progress_list.lock }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); }
*************************************************************
這裏有個小技巧
i ^ 1 按位異或運算符
所以實際上第二個傳參數是1、0索引對調了,所以取值是failList.disable與doneList.disable
*************************************************************
通過stateString有值這個條件,預先向doneList,failList中的list添加三個回調函數
分別是:
doneList : [changeState, failList.disable, processList.lock]
failList : [changeState, doneList.disable, processList.lock]
- changeState 改變狀態的匿名函數,deferred的狀態,分爲三種:pending(初始狀態), resolved(解決狀態), rejected(拒絕狀態)
- 不論deferred對象最終是resolve(還是reject),在首先改變對象狀態之後,都會disable另一個函數列表failList(或者doneList)
- 然後lock processList保持其狀態,最後執行剩下的之前done(或者fail)進來的回調函數
所以第一步最終都是圍繞這add方法
- done/fail/是list.add也就是callbacks.add,將回調函數存入回調對象中
第二部分很簡單,給deferred對象擴充6個方法
- resolve/reject/notify 是 callbacks.fireWith,執行回調函數
- resolveWith/rejectWith/notifyWith 是 callbacks.fireWith 隊列方法引用
最後合併promise到deferred
promise.promise( deferred );
jQuery.extend( obj, promise )
所以最終通過工廠方法Deferred構建的異步對象帶的所有的方法了
return 內部的deferred對象了
由此可見我們在
var defer = $.Deferred(); //構建異步對象
的時候,內部的對象就有了4個屬性方法了
- deferred: Object
- always: function () {
- done: function () {
- fail: function () {
- notify: function () {
- notifyWith: function ( context, args ) {
- pipe: function ( /* fnDone, fnFail, fnProgress */ ) {
- progress: function () {
- promise: function ( obj ) {
- reject: function () {
- rejectWith: function ( context, args ) {
- resolve: function () {
- resolveWith: function ( context, args ) {
- state: function () {
- then: function ( /* fnDone, fnFail, fnProgress */ ) {
- promise: Object
- always: function () {
- done: function () {
- fail: function () {
- pipe: function ( /* fnDone, fnFail, fnProgress */ ) {
- progress: function () {
- promise: function ( obj ) {
- state: function () {
- then: function ( /* fnDone, fnFail, fnProgress */ ) {
- state: "pending"
- tuples: Array[3]
構造圖
以上只是在初始化構建的時候,我們往下看看動態執行時候的處理
*****************執行期***********************
一個最簡單的demo爲例子
var d = $.Deferred(); setTimeout(function(){ d.resolve(22) },0); d.then(function(val){ console.log(val); })
換句話說,我們調用d.resolve(22) 就等於是調用
匿名函數並傳入參數值 22
function(val){ console.log(val); //22 }
當前實際的使用中會有各種複雜的組合情況,但是整的外部調用流程就是這樣的
***************** resolve的實現 *******************
我們回顧下,其實Deferred對象,內部的實現還是Callbacks對象,只是在外面再封裝了一層API,供接口調用
d.resolve(22)
實際上調用的就是通過這個代碼生成的
deferred[ tuple[0] ] = function() { deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); return this; };
deferred[ tuple[0] + "With" ] = list.fireWith;
deferred.resolveWith()
最終執行的就是 list.fireWith
所以最終又回到回調對象callbacks中的私有方法fire()了
Callbacks會通過
callbacks.add()
把回調函數給註冊到內部的list = []上,我們回來過看看
deferred.then()
d.then(function(val){ console.log(val); })
***************** then的實現 *******************
then: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; return jQuery.Deferred(function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { var action = tuple[ 0 ], fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; // deferred[ done | fail | progress ] for forwarding actions to newDefer deferred[ tuple[1] ](function() { //省略............ }); }); fns = null; }).promise(); },
- 遞歸jQuery.Deferred
- 傳遞了func
- 鏈式調用了promise()
因爲在異步對象的方法都是嵌套找作用域屬性方法的
這裏我額外的提及一下作用域
var d = $.Deferred();
這個異步對象d是作用域是如何呢?
第一層:無可爭議,瀏覽器環境下最外層是 window
第二層:jquery本身是一個閉包
第三層: Deferred工廠方法產生的作用域
如果用d.then()方法呢?
很明顯then方法又是嵌套在內部的函數,所以執行的時候都默認會包含以上三層作用域+自己本身函數產生的作用域了
我們用個簡單圖描繪下
根據規則,在最內部的函數能夠訪問上層作用域的所有的變量
我們先從使用的層面去考慮下結構設計:
demo 1
var defer = $.Deferred(); var filtered = defer.then(function( value ) { return value * 2; }); defer.resolve( 5 ); filtered.done(function( value ) { console.log(value) //10 });
demo 2
var defer = $.Deferred(); defer.then(function(value) { return value * 2; }).then(function(value) { return value * 2; }).done(function(value) { alert(value) //20 }); defer.resolve( 5 );
其實這裏就是涉及到defer.then().then().done() 鏈式調用了
API是這麼定義的:
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
我們抓住幾點:
- 返回的是新的promise對象
- 內部有一個濾器函數
從demo 1中我們就能看到
經過x.then()方法處理的代碼中返回的this(filtered ),不是原來的$.Deferred()所有產生的那個異步對象(defer )了
所以,每經過一個then那麼內部處理的this都要被重新設置,那麼爲什麼要這樣處理呢?
源碼
then: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; //分別爲deferred的三個callbacklist添加回調函數,根據fn的是否是函數,分爲兩種情況 return jQuery.Deferred(function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { var action = tuple[ 0 ], fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; // deferred[ done | fail | progress ] for forwarding actions to newDefer deferred[ tuple[1] ](function() { var returned = fn && fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise() .done( newDefer.resolve ) .fail( newDefer.reject ) .progress( newDefer.notify ); } else { newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); } }); }); fns = null; }).promise(); },
在Deferred傳遞實參的時候,支持一個flag,jQuery.Deferred(func)
傳遞一個回調函數
// Call given func if any if ( func ) { func.call( deferred, deferred ); }
所以newDefer可以看作是
newDefer = $.Deferred();
那麼func回調的處理的就是過濾函數了
deferred[ tuple[1] ](function() { var returned = fn && fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise() .done( newDefer.resolve ) .fail( newDefer.reject ) .progress( newDefer.notify ); } else { newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); } });
這裏其實也有編譯函數的概念,講未來要執行的代碼,預先通過閉包函數也保存起來,使其訪問各自的作用域
第一步
分解tuples元素集
jQuery.each( tuples, function( i, tuple ) { //過濾函數第一步處理 })
第二步
分別爲deferred[ done | fail | progress ]執行對應的add方法,增加過濾函數給done | fail | progress 方法
deferred[ tuple[1] ](傳入過濾函數
)//過濾函數 執行的時候在分解
代碼即
deferred[done] = list.add = callback.add
第三步
返回return jQuery.Deferred().promise()
此時構建了一個新的Deferred對象,但是返回的的是經過promise()方法處理後的,返回的是一個受限的promise對象
所以整個then方法就處理了2個事情
- 構建一個新的deferred對象,返回受限的promise對象
- 給父deferred對象的[ done | fail | progress ]方法都增加一個過濾函數的方法
我們知道defer.then方法返回的是一個新的jQuery.Deferred().promise()對象
那麼我們把defer.then返回的稱之爲子對象,那麼如何與父對象var defer = $.Deferred() 關聯的起來的
我看看源碼
deferred[ tuple[1] ](//過濾函數//)
deferred其實就是根級父對象的引用,所以就嵌套再深,其實都是調用了父對象deferred[ done | fail | progress 執行add罷了
從圖中就能很明顯的看到 2個不同的deferred對象中 done fail progress分別都保存了不同的處理回調了
deferred.resolve( args )
- 當延遲對象被 resolved 時,任何通過
deferred.then
或deferred.done
添加的 doneCallbacks,都會被調用 - 回調函數的執行順序和它們被添加的順序是一樣的
- 傳遞給
deferred.resolve()
的args
參數,會傳給每個回調函數 - 當延遲對象進入 resolved 狀態後,再添加的任何 doneCallbacks,當它們被添加時,就會被立刻執行,並帶上傳入給
.resolve()
的參數
流程如圖
流程解析:
1 執行fire()方法,遞歸執行list所有包含的處理方法
2 執行了默認的 changeState, disable, lock 方法、
3 執行過濾函數
根據 var returned = fn.apply( this, arguments )的返回值(稱作returnReferred)是否是deferred對象
- 返回值是deferred對象,那麼在returnReferred對象的三個回調函數列表中添加newDeferred的resolve(reject,notify)方法,也就是說newDeferrred的執行依賴returnDeferred的狀態
- 不是函數的情況(如值爲undefined或者null等),直接鏈接到newDeferred的resolve(reject,notify)方法,也就是說 newDeferrred的執行依賴外層的調用者deferred的狀態或者說是執行動作(resolve還是reject或者是notify) 此時deferred.then()相當於將自己的callbacklist和newDeferred的callbacklist連接起來
下面就是嵌套deferred對象的劃分了
源碼還是要靠自己去折騰的
思想的提高比較難的,我們可以借鑑設計的思路,代碼書寫方式都是有益無害的
流程的分析已經比較透徹了,下一章在講解when的實現
寫這東西太耗精力了,如果對您有幫助,請點擊推薦支持一下……………