深入解析jQuery中的延时对象的概念
第一個作用,解決時序以及動態添加執行函數的問題。
function a(){alert(1)}; function b(){alert(2)}; function c(){alert(3)}; a(); setTimeout(function(){b();},0); c();很明顯函數執行順序是a->c->b,而不是按照函數添加的順序執行。如果我要保證函數按順序執行,那么c 必須寫緊跟在b后面執行
setTimeout(function(){b(); c();},0);如果添加的函數按順序執行,比如添加在b后面的函數都在b執行后執行(如果b已經執行過了,那么久馬上執行)。如果按照這種方式,我們必須在執行之前就知曉說有要執行的函數,然后都加到setTimeout里面去,這個限制非常可惡。我希望的是即使b執行過了,我后面還能動態的添加函數并得到執行。比如
var defer = $.Deferred(); //構建異步對象function a(){alert(1)}; function b(){alert(2)};
//添加函數
defer.done(a);
//添加函數
defer.done(b);setTimeout(function(){
defer.resolve();//alert(1),alert(2)
},0);//添加函數
defer.done(function c(){alert(3)});//馬上執行出結果alert(3)
第二,解決參數傳遞的問題,所有的執行函數需要的參數相同,我希望我只傳遞一個參數就將所有的結果執行出來,特別是后續添加的函數執行我也希望使用原來的參數。
function a(value){alert("a = " value)}; function b(value){alert("b = " value)}; var list = [],val = 0;; list[list.length] = a; list[list.length] = b; function runList(listArray,value){for(var i = 0; i < listArray.length; i ){listArray[i](value);}listArray.length = 0; } val = 5; runList(list,val );//執行a = 5;b = 5; list[list.length] = function c(value){alert("c = " value)}; runList(list,val);//執行結果c = 5在保證runList函數有自己的作用域,不使用外部變量的情況下,那么我們每次執行runList都需要重新傳遞list和val這個變量。這種重復勞動沒有任何意義。看看deferred的處理方式
var defer = $.Deferred(); //構建異步對象 function a(value){alert("a = " value)}; function b(value){alert("b = " value)}; defer.done(a,b);//添加函數 defer.resolve(5);//執行結果a = 5; b = 5; defer.done(function c(value){alert("c = " value)});//添加函數,執行結果c = 5首先你要承認的是代碼簡化和可讀性都高很多,其次,傳遞的執行參數值5只需要一次便足夠,哪怕是后面添加的函數c也會用先前傳遞過來的參數執行。
Deferred函數將Callbacks函數抽離出去以后Deferred函數變得很精簡。如下
jQuery.extend({Deferred : function(){…}when : function(){…} )}其中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 */ ) {…},promise: function( obj ) {…}},deferred = {}; //兼容老版本promise.pipe = promise.then; // 添加promise的三個方法[done | fail | progress]方法// 添加deferred的六個方法[resolve |resolveWith | reject | rejectWith | notify | notifyWith]
jQuery.each( tuples, function( i, tuple ) {...}); // Make the deferred a promisepromise.promise( deferred );... return deferred;}
主要做了幾個工作。
deferred.done(fns) | 將回調添加到成功回調列表(doneCallbacks),當Deferred(延遲)對象解決(調用resolve)時會執行成功回調列表中的所有函數。參數fns可以是函數,也可以是函數數組 |
?deferred.fail(fns) | ?將回調添加到失敗回調列表(failCallbacks),當Deferred(延遲)對象拒絕(調用reject)時會執行失敗回調列表中的所有函數。參數fns可以是函數,也可以是函數數組 |
deferred.always(fns) | 將回調添加到成功回調列表(doneCallbacks和失敗回調列表(failCallbacks)。確保當Deferred(延遲)對象解決(調用resolve)或拒絕(調用reject)時,都會執行到該回調。參數fns可以是函數,也可以是函數數組 |
? deferred.progress(fns) | ?將回調添加到進度回調列表(progressCallbacks),當Deferred(延遲)對象生成進度通知(調用notify)時,執行進度回調列表中的所有函數。參數fns可以是函數,也可以是函數數組 |
? deferred.resolve(args) | ?解決Deferred(延遲)對象,并根據給定的args參數執行成功回調列表(doneCallbacks)的所有函數。 |
? deferred.resolveWith(context, args) | ?解決Deferred(延遲)對象,并根據給定的 context和args參數執行成功回調列表(doneCallbacks)的所有函數。這是jQuery.Callbacks的內部方法fireWith的引用,deferred.resolve()方法內部調用該方法來實現。不建議外部使用。 |
? deferred.reject(args) | ?拒絕Deferred(延遲)對象,并根據給定的args參數調用失敗回調列表(failCallbacks)中的所有函數。 |
? deferred.rejectWith(context, args) | ?拒絕Deferred(延遲)對象,并根據給定的 context和args參數執行失敗回調列表(failCallbacks)的所有函數。這是jQuery.Callbacks的內部方法fireWith的引用,deferred.reject()方法內部調用該方法來實現。不建議外部使用。 |
? deferred.notify(args) | ?根據給定的 args參數 調用Deferred(延遲)對象上進度回調列表(progressCallbacks)的所有函數。 |
? deferred.notifyWith(contex,args) | ?根據給定的上下文(context)和args遞延調用Deferred(延遲)對象上進度回調列表(progressCallbacks )上的所有函數。這是jQuery.Callbacks的內部方法fireWith的引用,deferred.notify()方法內部調用該方法來實現。不建議外部使用。 |
? deferred.state() | ?確定一個Deferred(延遲)對象的當前狀態。"pending"、"resolved"、"rejected" |
? deferred.pipe()/deferred.then() | ?當Deferred(延遲)對象解決(resolve)、拒絕(reject)或生成進度(notify),調用相應回調列表的所有函數。 |
? jQuery.Deferred() | ??一個構造函數,返回一個延時對象。 |
? jQuery.when(subordinate /* , ..., subordinateN */) | ?提供一種方法來執行一個或多個對象的回調函數, Deferred(延遲)對象通常表示異步事件。 |
? .promise() | ?返回一個 Promise 對象用來觀察當某種類型的所有行動綁定到集合,排隊與否還是已經完成 |
a. $.Deferred()詳解
關鍵源碼如下
Deferred: function( func ){var tuples = [ // 執行名, 添加監聽器(回調), 監聽列表(回調列表), 最終狀態[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],[ "notify", "progress", jQuery.Callbacks("memory") ]],state = "pending",promise = {...promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise;}},deferred = {};// 添加列表指定的方法 jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ],stateString = tuple[ 3 ]; // promise[ done | fail | progress ] = list.add promise[ tuple[1] ] = list.add; // 只有tuples[0]和tuples[1]進入該分支 if ( stateString ) { //給Callbacks對象添加回調列表 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 );} // deferred[ resolve | reject | notify ] deferred[ tuple[0] ] = function() {deferred[ tuple[0] "With" ]( this === deferred ? promise : this, arguments ); return this;};deferred[ tuple[0] "With" ] = list.fireWith;}); // 給deferred添加promise的函數接口 promise.promise( deferred ); ...
return deferred; }
我們把tuples[index][2]中的jQuery.Callbacks返回的對象所代表的回調列表分別取名叫做doneCallbacks(成功回調列表),failCallbacks(失敗回調列表),progressCallbacks(進度回調列表)。則到函數執行到promise.promise( deferred );之前。promise變量變成了
promise = {state: function() {…},always: function() {…},then: function( /* fnDone, fnFail, fnProgress */ ) {…},promise: function( obj ) {…},done: doneCallbacks.add,fail: failCallbacks.add,progress: progressCallbacks.add }deferred變量變成了
deferred = {resolve: function() {deferred["resolveWith" ]( this === deferred ? promise : this, arguments );return this;},resolveWith: doneCallbacks.fireWith,reject: function() {deferred["rejectWith" ]( this === deferred ? promise : this, arguments );return this;},rejectWith: failCallbacks.fireWith,notify: function() {deferred["notifyWith" ]( this === deferred ? promise : this, arguments );return this;},notifyWith: progressCallbacks.fireWith }初始化結束后doneCallbacks中回調隊列如下
[function(){state = “resolved”:},failCallbacks.disable,progressCallbacks.lock ]初始化結束后failCallbacks中回調隊列如下
[function(){state = “rejected”;},doneCallbacks.disable,progressCallbacks.lock ]初始化結束后progressCallbacks的回調列表中沒有任何回調
需要注意的是doneCallbacks、failCallbacks的設置是”once memory”,progressCallbacks設置是”memory”
這樣,應當好理解Deferred的原理了。我們使用done/fail是”once memory”設置下的add。意味著如果如果先執行了resolve/ reject方法,后調用done/fail則會直接執行回調。
eg:
var defer = $.Deferred(); //構建異步對象//執行doneCallbacks的回調,保存場景,這個時候doneCallbacks有三個回調函數,依序執行。其中第二個回//調禁用failCallbacks(禁用reject/rejectWith),第三個回調鎖progressCallbacks(鎖notify/notifyWith) defer.resolve( 5 ); defer.done(function( value ) {console.log('打印出值',value) });//doneCallbacks.add,立刻執行回調
如果是先調用done/fail,后執行resolve/reject。根據”once memory”將會先添加回調,然后fire。
eg:
var defer = $.Deferred(); //構建異步對象 //1秒鐘后執行doneCallbacks回調,保存場景。這個時候的doneCallbacks有四個回調函數,依序執行 setTimeout(function(){defer.resolve( 5 ); },1000); //根據”once memory”,第一次fire之前會把回調添加到doneCallbacks中。等待調用 defer.done(function( value ) {console.log('打印出值',value)});當延遲對象被 resolved 時,任何通過 deferred.then或deferred.done 添加的 doneCallbacks,都會被調用。回調函數的執行順序和它們被添加的順序是一樣的。傳遞給 deferred.resolve() 的 args 參數,會傳給每個回調函數。當延遲對象進入 resolved 狀態后,再添加的任何 doneCallbacks,當它們被添加時,就會被立刻執行,并帶上傳入給 .resolve()的參數
?b. Deferred.promise.then詳解
API是這么定義的:
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
從jQuery 1.8開始, 方法返回一個新的promise(承諾),通過一個函數,可以過濾deferred(延遲)的狀態和值。替換現在過時的deferred.pipe()方法。 doneFilter 和 failFilter函數過濾原deferred(延遲)的解決/拒絕的狀態和值。 progressFilter 函數過濾器的任何調用到原有的deferred(延遲)的notify 和 notifyWith的方法。
這些過濾器函數可以返回一個新的值傳遞給的 promise(承諾)的.done() 或 .fail() 回調,或他們可以返回另一個觀察的對象(遞延,承諾等)傳遞給它的解決/拒絕的狀態和值promise(承諾)的回調。
如果過濾函數是空,或沒有指定,promise(承諾)將得到與原來值相同解決(resolved)或拒絕(rejected)。
源碼解析:
then: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments;return jQuery.Deferred(function( newDefer ) {//遍歷tuples,將fnDone, fnFail, fnProgress分別綁定到tuples[0-2][2]的回調列表上jQuery.each( tuples, function( i, tuple ) {var action = tuple[ 0 ],//resolve/reject/notifyfn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; //執行deferred[ done | fail | progress ] 函數,將回調fnDone, fnFail, fnProgress分別添加到回調列表 //需要明確的是這些回調函數是添加到deferred中的,不是newDefer中的。deferred[ tuple[1] ](function() { //執行fnDone, fnFail, fnProgress函數 var returned = fn && fn.apply( this, arguments ); //如果返回的是Deferred對象或是Deferred.promise對象 if ( returned && jQuery.isFunction( returned.promise ) ) { //當Deferred(延遲)對象解決/拒絕/生成進度通知時//調用添加處理程續resolve/reject/notifyreturned.promise().done( newDefer.resolve ).fail( newDefer.reject ).progress( newDefer.notify );} else { //執行resolveWith/rejectWith/notifyWith函數執行所有剩余回調 newDefer[ action "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );}});});fns = null;}).promise(); }。我們抓住幾點:
返回的是新的promise對象
內部有一個過濾器函數
我們將最外層初始化的延時對象叫做主延時對象mainDeferred,經過mainDeferred.then()方法處理的代碼中返回的jQuery.Deferred(...).promise(),不是原來的mainDeferred了,我們把它命名為thenDeferred。thenDeferred初始化的時候有這個一段
// 調用給定的func參數if ( func ) {func.call( deferred, deferred );}剛好thenDeferred的初始化帶有函數參數我們命名為thenParamfunc。thenParamfunc的參數是newDefer,結合上面執行func的代碼我們可知,這個newDefer就是thenDeferred嘛,饒了半天。thenParamfunc中做一個操作:
往mainDeferred的三個回調列表中添加回調,每個回調內部會執行mainDeferred.then添加的對應的回調函數。
//deferred是主延時對象mainDeferreddeferred[ tuple[1] ](function() {
//執行fnDone, fnFail, fnProgress函數
var returned = fn && fn.apply( this, arguments );
... });
還需要注意的是當這些回調被調用時,mainDeferred.then添加的回調執行后,會執行下面的代碼
newDefer[ action "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );這表明mainDeferred.then生成的延時對象thenDeferred的狀態依賴于主延時對象mainDeferred。當mainDeferred解決(resolve),那么thenDeferred也會被解決(resolve),并且mainDeferred.then設置的doneFilter的返回值將作為參數傳遞給thenDeferred的回調列表。
eg:
var deferred = $.Deferred(); var m = deferred .done(function(val){console.log('done function ' val)}) .then(function(val){console.log('done then ' val); return 10;}, function(val){console.log('fail then ' val);}, function(val){console.log('progress then' val);}) //主延時對象解決//打印done function 5;done then 5;
deferred.resolve(5);
//執行then deferred done 10
m.done(function(val){console.log("then deferred done " value)})
現在完整的分析一個例子的數據
eg:
var deferred = $.Deferred(); deferred .done(function(val){console.log('done function ' val)}) .then(function(val){console.log('done then' val);}, function(val){console.log('fail then' val);}, function(val){console.log('progress then' val);}) deferred.resolve(5);說明:
deferred.done(...).then(...)執行后,done的回調列表doneCallbacks是下面的樣子
doneCallbacks = [function () { // state = [ resolved | rejected ]state = stateString; // [ reject_list | resolve_list ].disable; progress_list.lock}, function () {list = stack = memory = undefined; return this;}, function () {stack = undefined; if ( !memory ) {self.disable();} return this;}, function (val){console.log('done function ' val)}, 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 );}}
]
其中doneCallbacks前面三個是Callbacks自帶的,第四個是.done添加的,
第五個這個是.then的第一個參數添加的,對應的fn是function(val){console.log('done then' val);}
fail的回調列表failCallbacks的樣式是
failCallbacks = [function () {// state = [ resolved | rejected ]state = stateString;// [ reject_list | resolve_list ].disable; progress_list.lock},function () {list = stack = memory = undefined;return this;},function () {stack = undefined;if ( !memory ) {self.disable();}return this;},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 );}}
]
前面三個元素是Callbacks自帶的,
第四個是.then的第二個參數添加的,對應的fn是function(val){console.log('fail then' val);}
progress對應回調列表progressCallbacks的樣子是
progressCallbacks = [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 );}}
]
只有一個元素是.then的第三個參數添加的,fn對應為function(val){console.log('progress then' val);})
總結
以上是生活随笔為你收集整理的深入解析jQuery中的延时对象的概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jQuery.extend() 使用语法
- 下一篇: jQuery获取隐藏域和radio单项框