深入理解jQuery中的Deferred
引入
1 在開發的過程中,我們經常遇到某些耗時很長的javascript操作,并且伴隨著大量的異步。
2 比如我們有一個ajax的操作,這個ajax從發出請求到接收響應需要5秒,在這5秒內我們可以運行其他代碼段,當響應到達后,我們需要判斷響應的結果(無非就是成功或者失敗),并 根據不同的結果 添加回調函數 。
3 為了有效的簡潔的 添加回調函數 jQuery引入了Callbacks。
4 而為了方便的 根據不同的結果( 或者根據各種跟結果有關的邏輯,比如不管是成功或者失敗) 添加回調函數,jQuery引入了Deferred。
$.ajax("test.html")
.done(function(){ alert("success"); })
.fail(function(){ alert("error"); });
5 因而Deferred與Callbacks是密不可分的,事實上,Callbacks也是從Deferred中分離出去的
回顧Callbacks
1 Callbacks大體架構
2 Callbacks源碼分析:
define([
"./core",
"./var/rnotwhite"
], function( jQuery, rnotwhite ) {
// String to Object options format cache
var optionsCache = {};
// Convert String-formatted options into Object-formatted ones and store in cache
/
如果: var a = $.Callback('once memory')
則 optionsCache中會有這么一項:"once memory":{memory:true,once:true}/
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true;
});
return object;
}
/*
- Create a callback list using the following parameters:
*
- options: an optional list of space-separated options that will change how
- the callback list behaves or a more traditional option object
*
- By default a callback list will act like an event callback list and can be
- "fired" multiple times.
*
- Possible options:
*
- once: will ensure the callback list can only be fired once (like a Deferred)
*
- memory: will keep track of previous values and will call any callback added
- after the list has been fired right away with the latest "memorized"
- values (like a Deferred)
*
- unique: will ensure a callback can only be added once (no duplicate in the list)
*
stopOnFalse: interrupt callings when a callback returns false
/
jQuery.Callbacks = function( options ) {
// Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );
var // Last fire value (for non-forgettable lists)
memory,
// Flag to know if list was already fired list是否已經被fire函數調用過
fired,
// Flag to know if list is currently firing 當前是否正在調用fire函數
firing,
// First callback to fire (used internally by add and fireWith) 第一個被執行的回調函數在list的位置
firingStart,
// End of the loop when firing fire函數要運行的回調函數的個數
firingLength,
// Index of currently firing callback (modified by remove if needed) 當前正在執行的回調函數的索引
firingIndex,
//回調函數數組
list = [],
// Stack of fire calls for repeatable lists 可重復的回調函數棧。我們可能會短時間內執行多次fire(),若當前的fire()正在迭代執行回調函數,而緊接著又執行了一次fire()時,會將下一次的fire()參數等保存至stack中,等待當前的fire()執行完成后,將stack中的fire()進行執行
stack = !options.once && [],
// Fire callbacks
fire = function( data ) {
// data[0] 是一個對象,data[1]則是回調函數的參數
memory = options.memory && data; // 很精妙,仔細體會一下這句代碼,如果調用Calbacks時傳入了memory,則memory = data,否則memory = false
fired = true; // 在調用本函數時,將fired狀態進行修改
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true; // 迭代回調函數之前,將firing狀態進行修改
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false &&
options.stopOnFalse ) { // 運行回調函數的同時,檢測回調函數是否返回false,若返回false,且調用Callbacks時傳入stopOnFalse參數,則終止迭代
memory = false; // To prevent further calls using add 既然終止迭代了,那么之后添加的回調函數都不應該被調用,將memory設置為false
break;
}
}
firing = false; // 迭代回調函數完成后,將firing狀態進行修改
if ( list ) {
if ( stack ) { // 沒有使用once參數
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) { // 使用了once memory參數,則在迭代完回調函數之后清空list
list = [];
} else { // 其他
self.disable();
}
}
},
// Actual Callbacks object
self = {
// 將一個新的回調函數添加至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 ); // 若參數中的元素為函數且(無unique參數或者list中沒有該函數),則將該函數添加至list末尾
}
} else if ( arg && arg.length && type !== "string" ) { // arg的長度不為0且每項的類型不為字符串,也就是args為這種情況:[[fun1,fun2...],[fun3,fun4]](不僅限于這種情況)
// Inspect recursively
add( arg );
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch?
// 當Callback中的firingLength變為 動態的! 也就是:只要我們向list中添加了一個新的回調函數,即使在fire()運行過程中,改變也能立即體現出來
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
} else if ( memory ) { // 如果當前沒有執行回調函數,且存在memory參數,則執行新添加的回調函數
firingStart = start;
fire( memory );
}
}
return this;
},
// Remove a callback from the list 將一個回調函數從list中移除
remove: function() {
if ( list ) {
jQuery.each( arguments, function( _, arg ) {
var index;
while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );
// Handle firing indexes
if ( firing ) {
if ( index <= firingLength ) {
firingLength--;
}
if ( index <= firingIndex ) {
firingIndex--;
}
}
}
});
}
return this;
},
// Check if a given callback is in the list.
// If no argument is given, return whether or not list has callbacks attached.
has: function( fn ) {
return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
},
// Remove all callbacks from the list 清空數組
empty: function() {
list = [];
firingLength = 0;
return this;
},
// Have the list do nothing anymore 使用了這個方法,則意味著該回調對象失效了。
disable: function() {
list = stack = memory = undefined;
return this;
},
// Is it disabled?
disabled: function() {
return !list;
},
// Lock the list in its current state 給數組上鎖
lock: function() {
stack = undefined;
if ( !memory ) {
self.disable();
}
return this;
},
// Is it locked?
locked: function() {
return !stack;
},
// Call all callbacks with the given context and arguments
// 使用傳入的context作為當前函數的執行上下文
fireWith: function( context, args ) {
if ( list && ( !fired || stack ) ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
if ( firing ) {
stack.push( args ); // 如果當前正在迭代執行回調函數,則將新的fire參數推入stack中
} else {
fire( args );
}
}
return this;
},
// Call all the callbacks with the given arguments
fire: function() {
self.fireWith( this, arguments );
return this;
},
// To know if the callbacks have already been called at least once
// 用來確定當前callback對象是否被fire()過
fired: function() {
return !!fired;
}
};
return self;
};
return jQuery;
});
源碼講解</code></pre>
另外要注意下面兩個參數:
once:如果創建Callbacks時加入該參數,則運行數組中的所有回調函數之后,也就是fire()之后,會清空數組。
memory:會保存上一次運行fire(args)時的參數args,每當添加一個新的回調函數到數組中,會立即使用args作為參數調用新加的函數一次
Deferred講解:
Deferred大體架構:

1 先來看一看tuple數組 :
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],// 解決 操作成功 Callbacks對象 最終狀態為解決
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], // 拒絕 操作失敗 Callbacks對象 最終狀態為拒絕
[ "notify", "progress", jQuery.Callbacks("memory") ] // 通知 操作進行中 Callbacks對象 最終狀態無(操作進行中的最終狀態就是操作完成,完成無非就是轉變為上面兩種 成功或者失敗)
]
jQuery的設計理念是這樣的:
1 deferred對象有三種執行狀態----完成 失敗 進行中。
2 每種狀態對應一個Callbacks實例
3 如果執行狀態是"完成"(resolved),deferred對象立刻調用done()方法指定的回調函數(也就是執行已完成狀態對應的Callbacks實例的fire方法);如果執行狀態是"失敗",調用fail()方法指定的回調函數;如果執行狀態是"進行中",則繼續等待,或者調用progress()方法指定的回調函數。
2 promise對象和deferred對象
一個是deferred外部接口對象,一個是內部promise對象。
promise對象是一個受限的對象, 這就是所謂的受限制的deferred對象,因為相比deferred對象, promise對象沒有resolve(With), reject(With), notify(With)這些能改變deferred對象狀態并且執行callbacklist的方法了,只能是then、done、fali等方法。
3 done fail progress 方法 與 resolve reject notify方法
在上圖中我們已經說明了前三個方法就是Callbacks中的add方法。后三個調用了Callbacks中的fireWith()方法。
所以,我們可以總結出:
1 三種狀態各對應一個Callbacks實例
2 使用done 或fail 或progress時,實際上就是往各自對應的Callbacks實例中的list數組添加回調函數
3 使用resolve 或reject 或notify時,則就是運行各自對應Callbacks實例中的list數組中的回調函數
4 then方法
then方法創建了一個新的promise對象,then就是pipe,我們可以想象是一個管道。管道就是能 ‘承上啟下’(更貼切的來說,在Deferred中的then只做了承上,僅僅是個人觀點)
var a = $.Deferred();
a.then(function(val){
console.log(val); // 2
return val * 2
}).then(function(val){
console.log(val); // 4
});
a.resolve(2)
如案例所示,下一個回調對象都能取到上一個回調對象的值,這樣一直可以疊加往后傳遞。
關于then可能看了非常迷糊,不要緊,上面說的是then的高級特性,平時我們基本不怎么使用的。
平時我們大部分是使用then來代替done以及fail:
$.when($.ajax( "test.php" ))
.then(successFunction, failureFunction );
deferred源碼分析
define([
"./core",
"./var/slice",
"./callbacks"
], function( jQuery, slice ) {
jQuery.extend({
Deferred: function( func ) {
// 創建一個tuples數組,一個promise對象,一個deferred對象,一個state變量
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],// 解決 操作成功 Callbacks對象 最終狀態為解決
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], // 拒絕 操作失敗 Callbacks對象 最終狀態為拒絕
[ "notify", "progress", jQuery.Callbacks("memory") ] // 通知 操作進行中 Callbacks對象 最終狀態無(操作進行中的最終狀態就是操作完成,完成無非就是轉變為上面兩種 成功或者失敗)
],
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 fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() { // 注意,這里的deferred指的不是新Deferred對象中的deferred(也就是不是指的newDefer)
var returned = fn && fn.apply( this, arguments );
// 若returned是一個deferred對象,則為returned添加一個回調函數,這個回調函數運行后使newDefer能夠接收到returned的fire()參數
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ tuple[ 0 ] + "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 ) { // 若obj非null,則將obj與promise對象結合并返回,否則返回promise對象
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 ], // 這是一個Callbacks 對象
stateString = tuple[ 3 ]; // 用字符串表示的狀態
// promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add; // Callbacks對象的add函數
// Handle state 處理狀態
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString; // Callbacks對象中的回調函數列表第一項:改變狀態
// [ reject_list | resolve_list ].disable; progress_list.lock
},
tuples[ i ^ 1 ][ 2 ].disable, // 因為reject,resolve是對立的,當行為為reject,那么resolve的Callbacks就無用了,將其回調函數列表清空即可
tuples[ 2 ][ 2 ].lock ); // 當行為為reject或者resolve時,即"結果已確定",那么就不允許再調用 "操作進行中" 的Callbacks對象的fire()了
}
// 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合并至deferred
promise.promise( deferred );
// Call given func if any 在初始化完deferred對象后,會立即運行func函數,并把deferred作為第一個參數傳入
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
},
// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
/* 大體思路:
* 若參數(參數必需是deferred對象)只有一個,則返回這個參數的promise對象
* 若參數有多個,則生成一個新的deferred對象,并返回deferred對象的promise對象
* 當所有參數的狀態為完成時,使新deferred對象的狀態變為完成,
* 若有一個參數的狀態為失敗,則使新deferred對象的狀態變為失敗
*
* */
var i = 0,
resolveValues = slice.call( arguments ),//slice是數組的slice方法,一般情況下:resolveValues就是一個由deferred組成的數組
length = resolveValues.length,
// the count of uncompleted subordinates 沒有運行完成的deferred對象的數量
// 如果length長度不為1或者subordinate是一個deferred對象則,remaining=length,否則remaining為0
remaining = length !== 1 ||
( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,//運算符優先級: 邏輯與邏輯或 優先級高于 ? :
// the master Deferred.
// If resolveValues consist of only a single Deferred, just use that.
// 如果remaining為1,則使用subordinate(這是一個deferred對象)即可,否則創建一個新的deferred對象
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
// Update function for both resolve and progress values
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
if ( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
},
progressValues, progressContexts, resolveContexts;
// Add listeners to Deferred subordinates; treat others as resolved
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
// 迭代resolveValues中的每一個deferred對象,為其添加不同狀態下的回調函數
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
.done( updateFunc( i, resolveContexts, resolveValues ) )
.fail( deferred.reject )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
}
// If we're not waiting on anything, resolve the master
if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
}
return deferred.promise();
}
});
return jQuery;
});
</code></pre>
來自:http://www.cnblogs.com/MnCu8261/p/6193353.html