jquery deferred在jquery1.6+版本中開始出現,其定義致力於解決事件處理異步回調過程。由於jquery之前在解決異步回調時候一直是個短板,deferred的出現無疑給jquery錦上添花!
先來看一下deferred的基本應用:
function a(){
var def=$.Deferred();
setTimeout(function(){
var a=10;
def.resolve(a);
},2000)
return def.promise();
}
$.when(a()).done(function(a){alert(a)});
函數a裏面定義一個setTimeout事件,模擬一般的ajax異步請求,我們在這個定時事件裏面定義一個局部變量a,然後通過修改deferred狀態進行傳參。最後在a函數裏面通過返回deferred的promise暴漏給外部一個該私有deferred的狀態。
然後用$.when函數檢測a函數返回狀態進行判斷是否執行done回調函數。
當定時時間達到2000毫秒之後a函數裏面的私有deferred對象狀態修改爲resolved,從而觸發了$.when檢測的done回調,並且在回調中的匿名函數傳入定時事件裏面的私有變量作爲參數進行回調處理。
這種應用在很大程度上解決了ajax異步回調函數處理事件。並且可以將ajax請求返回值通過deferred狀態參數返回給外部處理函數進行使用。這是deferred最爲廣泛也是最爲實用的地方!
========================================================================================================
接下來我們通過jquery源碼分析一下deferred對象、resolve等狀態、promise對象、$.when以及回調函數整個處理過程!
先上Deferred對象在jquery裏面定義的源碼如下:
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() {
return state;
},
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
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() {
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();
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {
return obj != null ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {};
// Keep pipe for back-compat
promise.pipe = promise.then;
// Add list-specific methods
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
// promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add;
// Handle state
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 );
}
// 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;
});
// Make the deferred a promise
promise.promise( deferred );
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
}
去除註釋 整個定義不到100多行代碼,整個定義利用工廠模式最後返回一個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") ]
],
首先定義一個數組。數組中子數組每個都代表當前一個狀態。宏觀看來這個數組是定義了兩組消息:
一組爲回調:done、fail、progress,
一組爲狀態通知:resolve、reject、notify。
其中的jQuery.Callbacks定義返回一對象,resolve done,fail reject,progress notify分別對應三組$.Callbacks對象。
該對象擁有一些方法可以對各種不同狀態進行添加禁止移除回調。這個我們在順序分析時候穿插分析。
然後定義一個初始狀態state="pending",聲明Deferred對象後默認的state是pending。通過resolve或者reject改變狀態修改後則變爲resolved或者rejected
創建promise對象,該對象包括state,always,then,promise方法;
進而聲明一個空的deferred對象,
接下來真正的進行處理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;
// Handle state
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 );
}
// 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;
});
// Make the deferred a promise
promise.promise( deferred );
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
循環處理開始定義的數組tuples,通過promise[tuple[1]]=list.add給promise添加回調。即:
promise.done=$.Callbacks("once memory").add;
promise.fail=$.Callbacks("once memory").add;
promise.progress=$.Callbacks("once memory").add;
判斷stateString狀態。如果存在狀態即resolved、rejected則執行回調list.add方法即:
$.Callbacks('once memory').add(function(){state=stateString},tuples[i^1][2].disable,tuples[2][2].lock);
執行之後resolve以及reject狀態的list都存在了上面三個函數
拿resolved狀態舉例:
self = {
// Add a callback or a collection of callbacks to the list
add: function() {
if ( list ) {
// First, we save the current length
var start = list.length;
(function add( args ) {
jQuery.each( args, function( _, arg ) {
var type = jQuery.type( arg );
if ( type === "function" ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && type !== "string" ) {
// Inspect recursively
add( arg );
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch?
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
} else if ( memory ) {
firingStart = start;
fire( memory );
}
}
return this;
},
}
$.Callbacks定義一個空的數組list[],返回一個self對象,該對象包含add函數,該函數包括一個匿名立即執行函數
(function add( args ) {
jQuery.each( args, function( _, arg ) {
var type = jQuery.type( arg );
if ( type === "function" ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && type !== "string" ) {
// Inspect recursively
add( arg );
}
});
})( arguments );
該匿名函數遍歷add參數即[(function(){state=stateString},tuples[i^1][2].disable,tuples[2][2].lock],
如果傳入參數是一個function類型則進而判斷該參數是否存在於list內,符合條件的話將參數壓入list數組
如果不是function則繼續循環執行add函數。
在此我們傳入三個參數分別是一個匿名function、self.disable以及self.lock函數。都會被壓入list數組中
即對resolved狀態處理之後該$.Callback函數中的list==[(function(){state=stateString},tuples[i^1][2].disable,tuples[2][2].lock];
注意此處的^操作符,表示按位異或,相當於將索引0,1改爲1,0也就是對應fail.disable,done.disable.
簡而言之:
【tuples循環對promise增加了三個方法:done、fail、progress
每個方法對應一個$.Callbacks中self對象的add方法
done以及fail對應的$.Callbacks中的list數組分別增加了三個函數。】
執行狀態改變時候對應的done方法中如果沒有回調函數情況下會有三個函數shift執行,如果有回調函數在三個函數執行完之後再執行回調。
接下來
爲deferred對象增加一個resolveWith函數,並將$.Callbacks返回的self對象的fireWith賦值給該函數。
deferred對象增加的resolve函數也就是執行resolveWith函數。該函數接受兩個參數(deferred,arguments)作用域環境以及傳入參數
promise.promise(deferred)將promise對象方法屬性增加給deferred對象。
至此爲止deferred對象包含的屬性方法如下:
如果$.Deferred(fuc)參數存在則執行該function在deferred環境下
即:func.call( deferred, deferred );
最後返回deferred對象;
----------------------------------------------------------------------------------------------------------------
至此爲止$.Deferred聲明一個異步執行隊列完成,這個隊列返回一個deferred對象
該deferred對象中有一個promise函數,該函數返回一個包含屬性方法如下的deferred對象:
該對象與deferred對象相比較缺少了notify(with)、reject(with)、resolve(with)六個方法。
也就是說該屬性是一個只讀的,不能通過其去修改deferred狀態,但是可以通過它以一個觀察者模式去檢測當前狀態。這樣一來就可以防止在作用域外去修改當前deferred狀態。
deferred.resolve以及resolveWith的區別在以上源碼中也可以看出來:前者可以接受一個參數並且默認當前作用域爲deferred對象,後者可以接受兩個參數:
第一個參數如果不傳則默認爲deferred如果傳參則以傳入對象作爲作用域,第二個則爲參數
即dtd.resolveWith({name:"yuchao"},'hello'),在回調
done(function(arg){console.log(arg+this.name)})
其中this指向{name:'yuchao'},arg則爲'hello'.
一開始舉得例子我們可以不用when函數去觸發,可以直接a().done(function(a){xxxx});
$.when作用就是一個實現多deferred異步調用。假設有兩個Deferred對象deferred1以及deferred2可以用$.when(deferred1,deferred2).done(function(){})
這樣必須等到兩個deferred都執行成功後才能執行done回調函數
=================================================================================================================
總結:
Deferred是生成一個對象,該對象對於不同狀態保存着不同的處理函數,例如resolve,在執行resolve方法後,就會執行done回調函數,
執行done回調函數時候會先依次執行上述壓入數組內三個函數即[function(){state=stateString},tuples[i^1][2].disable,tuples[2][2].lock],最後執行最後壓入的真正回調函數done(fn);
上述分析僅爲自己根據源碼以及應用進行分析,如果表述有誤或有不對地方 還請大家不吝賜教!!!