终于抽出点时间来讲解一下jQuery中的Callbacks/Deferred这几个对象了,不过篇幅可能过长,讲到哪儿是哪儿吧。
$.Deferred方法每次调用都会返回一个Deferred对象。Deferred对象用于异步方法链式调用,让编程方式从传统的回调函数中解脱出来——javascript的回调深度一直都是遭人诟病。
首先从最底层Callbacks对象讲起,看简化后的源码:
Callbacks: function(options){
var list = [],
firing, fired, memory,
firingIndex, firingStart, firingLength,
options = options || {},
stack = !options.once && [],
fire = function(data){
memory = options.memory && data;
firingIndex = firingStart || 0;
firingStart = 0;
fired = true;
firingLength = list.length;
firing = true;
for(; firingIndex < firingLength; firingIndex++){
if(list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse === true){
memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if(list){
if(stack){
if(stack.length){
fire(stack.shift());
}
}else if(memory){
list = [];
//if options.once then disable it.
}else{
self.disable();
}
}
},
self = {
add: function(){
if(list){
var start = list.length;
(function _add(args){
iBen.each(args, function(arg){
if(jQuery.isFunction(arg)){
list.push(arg);
} else if ( arg && arg.length && !jQuery.isString(arg) ) {
// Inspect recursively
_add( arg );
}
});
})(arguments);
if(firing){
firingLength = list.length;
}else if(memory){
firingStart = start;
fire(memory);
}
}
return this;
},
// Remove a callback from the 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;
},
fireWith: function(context, args){
if(list){
args = [context, args];
if(firing){
stack.push(args);
}else{
fire(args);
}
}
return this;
},
fire: function(){
return this.fireWith(this, arguments);
},
disable: function(){
list = stack = memory = undefined;
return this;
}
};
return self;
}
我就不逐行写注释了,简单来说最后返回的self才是真正的Callbacks对象,公布出的接口主要也就是add、fire、fireWith、remove这几个方法。
add方法将函数添加到回调列表list中。
_add方法在参数是数组的时候会递归调用。
如果正在调用的话,修正回调列表的长度。
如果不是调用中且没有指明once选项同时指定了memory选项去记住最后一次调用的参数时则修正开始调用索引去立即调用。
fire方法实际调用的是Callbacks中的fire方法。
调用fireWith-指定默认环境为self和参数。
将参数进行格式化,如果正在调用则放到stack中等待当前调用列表完成后调用,否则立即调用。
进行fire时会通过options.once判断改列表是否只调用一次,是则调用结束后disable掉该回调列表,否则查看stack中是否有函数等待回调,如果指定了memory选项去记录了最后一次调用的参数,则清空该回调列表。
remove方法将对应的函数从回调列表中移除,并修正firing的索引。
整个Callbacks的流程就是这样,其实Callbacks可以看做为一个观察者模式。看观察者模式的代码:
observer : (function(box){
var Publisher={
subscribe: function(fn, context, type){
type = type || 'any';
fn = iBen.isFunction(fn) ? fn: context[fn];
if (iBen.isUndefined(this.subscribers[type]))
this.subscribers[type] = [];
this.subscribers[type].push([fn, context]);
},
unsubscribe: function(fn, type){
type = type || 'any';
var that = this;
this.subscribers[type].splice($A(this.subscribers[type]).indexOf(fn), 1);
},
shutdown: function(type){
type = type || 'any';
this.subscribers[type] = [];
},
publish: function(){
var args, type = arguments[arguments.length-1];
type = iBen.isString(type)? type : 'any';
args = _slice.call(arguments, 0);
this.subscribers[type]&&iBen.each(this.subscribers[type], function(object){
object[0].apply(object[1], args);
});
}
};
return function(o){
o = o || {};
iBen.extend(o, Publisher, true, 'function');
o.subscribers={any : []};
return o;
};
})()
基本上工作的流程是一样的,都是将对应的事件添加到队列中去,然后通过fire/publish的方式调用整个回调列表,除了Callbacks对象可以在函数的执行中通过self对象提供的接口更细粒度的控制和获取回调列表的运行状态。
Callbacks对象相对比较直观,下面看jQuery的Deferred对象,不过在这之前最好先自行了解一下Promise/A+规范:
要求
Promise的状态
一个 Promise 必须处于等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)这三种状态中的一种之中。
-
处于等待态时,promise :
- 可以迁移至执行态或拒绝态
-
处于执行态时,promise :
- 不能迁移至其他任何状态
- 必须拥有一个不可变的值
-
处于拒绝态时,promise:
- 不能迁移至其他任何状态
- 必须拥有一个不可变的据因
这里的不可变指的是恒等(即可用 ===
判断相等),而不是意味着更深层次的不可变
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 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[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).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;
// 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;
});
promise.promise( deferred );
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
}
可以看出,jQuery并没有完全按照Promise规范来构建promise对象。jQuery的三种状态分别为:resolved,rejected和处理中状态,而对应promise规范中的执行状态和被拒绝状态时状态不可改变的要求:
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会讲0和1的索引对调(在我的另一篇博文中有提到),在执行resolve时会将reject列表disable掉,同样在执行reject中会disable掉resolve列表,并将notify列表lock住,以防止状态的改变。
Deferred对象里面主要包含两个部分:
1、promise对象
在遍历tuples时会将对应的Callbacks对象的add方法作为promise的done | fail | progress。
2、deferred对象
在遍历tuples时会将对应的Callbacks对象的fireWith方法作为resolveWith | rejectWith | notifyWith
调用promise.promise将deferred对象做成一个promise对象。
最后返回deferred对象。
大概讲到这里吧,剩下的then方法和when对象留到下次讲解。
有其他疑问或错误之处欢迎之处~