deferred對象就是jQuery的回調函數解決方案,它解決瞭如何處理耗時操作的問題,比如一些Ajax操作,動畫操作等。(P.s:緊跟上一節:https://www.cnblogs.com/greatdesert/p/11433365.html的內容) 非同步隊列有三種狀態:待定(pending)、 ...
deferred對象就是jQuery的回調函數解決方案,它解決瞭如何處理耗時操作的問題,比如一些Ajax操作,動畫操作等。(P.s:緊跟上一節:https://www.cnblogs.com/greatdesert/p/11433365.html的內容)
非同步隊列有三種狀態:待定(pending)、成功(resolved)和失敗(rejected),初始時處於pending狀態
我們可以使用jQuery.Deferred創建一個非同步隊列,返回一個對象,該對象含有如下操作:
- done(fn/arr) ;添加成功回調函數,當非同步隊列處於成功狀態時被調用,參數同:jQuery.Callbacks(flags).add(fn/arr)
- fail(fn/arr) ;添加失敗回調函數,參數同上
- progress(fn/arr) ;添加消息回調函數,參數同上
- then(donefn/arr,failfn/arr,profn/arr) ;同時添加成功回調函數、失敗回調函數和消息回調函數
- always(fn/arr) ;添加回調函數到doneList和failList中,即保存兩份引用,當非同步隊列處於成功或失敗狀態時被調用 ;參數可以是函數、函數列表
- state() ;返回非同步隊列的當前狀態、返回pending、resolved或者rejected
- promise(obj) ;如果設置了obj參數對象則為obj對象增加非同步隊列的方法並返回。如果未設置參數obj,則返回當前Deferred對象的只讀副本
如果調用$.Diferred()創建一個非同步隊列,返回後的對象可以調用如下幾個方法:
writer by:大沙漠 QQ:22969969
- resolve(args) ;使用指定的參數調用所有的成功回調函數,非同步隊列進入成功狀態,之後就無法再調用
- reject(args) ;使用指定的參數調用所有的失敗回調函數,非同步隊列進入失敗狀態
- notify(args) ;使用指定的參數調用所有的消息回調函數
- resolveWith(context,args) ;使用指定的上下文和參數調用所有的成功回調函數,非同步隊列進入成功狀態
- rejectWith(context,args) ;使用指定的上下文和參數調用所有的失敗回調函數,非同步隊列進入失敗狀態
- notifyWith(context,args) ;使用指定的上下文和參數調用所有的消息回調函數
是不是和上一節介紹的Callbacks的fire和fireWith很像,Defferred內部就是通過通過Callback()回調函數列表來實現的,例如:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://libs.baidu.com/jquery/1.7.1/jquery.js"></script> </head> <body> <script> function f1(x){console.log('f1 '+x);} function f2(x){console.log('f2 '+x);} function f3(x){console.log('f3 '+x);} var t = $.Deferred(); //創建非同步隊列 t.done(f1).fail(f2).progress(f3); //添加成功回調函數、失敗回調函數和消息列表回調函數。等同於t.then(f1,f2,f3); t.notify('test'); //觸發所有消息函數列表,輸出:f3 test t.resolve('done'); //觸發所有成功回調函數,輸出:done t.reject('fail'); //沒有輸出,觸發成功回調函數後失敗回調函數列表就會被禁用,反過來也如此 t.progress(f1); //輸出:f1 test。這是因為消息回調函數之前已經被調用過,保存了上下文和參數了,如果之前沒有調用,這裡就沒有輸出。 </script> </body> </html>
輸出如下:
源碼分析
非同步隊列內部的實現原理就是通過jQuery.Callbacks定義三個回調函數列表,分別存儲成功、失敗、消息回調函數,然後返回一個對象,在該對象上設置done、fail和progress,分別對應這三個回調函數列表的add方法,這樣就實現分別添加不同狀態的回調函數了
$.Deferred是通過jQuery.extend函數直接掛載到jQuery里的,結構如下:
jQuery.extend({ Deferred: function( func ) { var doneList = jQuery.Callbacks( "once memory" ), //成功回調函數列表 failList = jQuery.Callbacks( "once memory" ), //失敗回調函數列表 progressList = jQuery.Callbacks( "memory" ), //消息回調函數列表 ;這三個回調函數列表就是存儲我們添加的觸發函數的 state = "pending", //初始狀態:pending lists = { //後面的方法會遍歷變數list中的屬性名來為非同步隊列添加方法deffred.resolve()、resolveWith()、reject()、rejectWith()、notify()和notifyWith() resolve: doneList, reject: failList, notify: progressList }, promise = { //非同步隊列的只讀副本 /*略*/ }, deferred = promise.promise({}), //非同步隊列 key; for ( key in lists ) { //添加觸發成功、失敗、消息回調函數的方法,執行後deferred對象就多了resolve()、resolveWith()、reject()、rejectWith()、notify()和notifyWith()方法。 deferred[ key ] = lists[ key ].fire; deferred[ key + "With" ] = lists[ key ].fireWith; } // Handle state deferred.done( function() { //添加三個成功回調函數,分別用於設置狀態為成功(resolved),禁用失敗列表函數列表和鎖定消息回調函數列表 state = "resolved"; }, failList.disable, progressList.lock ).fail( function() { //添加三個失敗回調韓素,分別用於設置狀態為失敗(rejected),禁用成功列表函數列表和鎖定消息回調函數列表 state = "rejected"; }, doneList.disable, progressList.lock ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; //返回非同步隊列deffrred }, //... })
可以看到$.Deferred最後返回的是deferred這個佈局變數,而deferred是promise.promise({})的返回值,我們來看看promise的實現方法:
promise = { //非同步隊列的只讀副本 done: doneList.add, //添加成功回調函數,引用對應的回調函數列表的callbacks.add(fn/arr)方法,下同 fail: failList.add, //添加失敗回調函數 progress: progressList.add, //添加消息回調函數 state: function() { //返回非同步隊列的當前狀態:pending、resolved、rejected之一 return state; }, // Deprecated isResolved: doneList.fired, isRejected: failList.fired, then: function( doneCallbacks, failCallbacks, progressCallbacks ) { //同時添加成功回調函數、失敗回調函數和消息回調函數 deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); return this; }, always: function() { //添加回調函數到doneList和failList中,即保存兩份引用,當非同步隊列處於成功或失敗狀態時被調用 deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); return this; }, pipe: function( fnDone, fnFail, fnProgress ) { //過濾當前非同步隊列的狀態和參數,並返回一個新的非同步隊列的副本,一般用不到 return jQuery.Deferred(function( newDefer ) { jQuery.each( { done: [ fnDone, "resolve" ], fail: [ fnFail, "reject" ], progress: [ fnProgress, "notify" ] }, function( handler, data ) { var fn = data[ 0 ], action = data[ 1 ], returned; if ( jQuery.isFunction( fn ) ) { deferred[ handler ](function() { returned = fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); } else { newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); } }); } else { deferred[ handler ]( newDefer[ action ] ); } }); }).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { //如果設置了obj參數對象則為obj對象增加非同步隊列的方法並返回。如果未設置參數obj,則返回當前Deferred對象的只讀副本, if ( obj == null ) { //如果obj為空 obj = promise; //則返回promise對象(是上一層作用域鏈的promise對象) } else { for ( var key in promise ) { //否則遍歷promise obj[ key ] = promise[ key ]; //將promise的所有方法、屬性保存到obj中 } } return obj; //最後返回obj } },
$.Deferred就是對Callbacks回調函數列表的管理而已,產生了一個新的$.Defferred介面,內部添加了一個state表示非同步隊列的狀態。