jquery 1.5 之 巧妙的Deferred
jquery 1.5 正式版已经出来半个月了,网上有不少blog对其评头论足,都提到说新加入一个Deferred函数
但始终没有一篇给力的东东让人一看就明白到底Deferred是个什么东东.粗略看了一下源码,研究一下它如何使用,在此做个记录......
首先Jquery里面多了一个$._Deferred和一个$.Deferred,由"_"符号可以看出来$._Deferred是不想被用户直接使用的内部处理函数,
而$.Deferred才是让用户调用的接口,但$.Deferred是以$._Deferred为基础的,所以我们必须先分析一下$._Deferred,
首先分析 $._Deferred 为了方便查看,我直接把 $._Deferred 源码拷贝过来了:
源码:
[javascript]
// Create a simple deferred (one callbacks list)
_Deferred: function() {
var // callbacks list
callbacks = [],
// stored [ context , args ]
fired,
// to avoid firing when already doing so
firing,
// flag to know if the deferred has been cancelled
cancelled,
// the deferred itself
deferred = {
// done( f1, f2, ...)
done: function() {
if ( !cancelled ) {
var args = arguments,
i,
length,
elem,
type,
_fired;
if ( fired ) {
_fired = fired;
fired = 0;
}
for ( i = 0, length = args.length; i < length; i++ ) {
elem = args[ i ];
type = jQuery.type( elem );
if ( type === "array" ) {
deferred.done.apply( deferred, elem );
} else if ( type === "function" ) {
callbacks.push( elem );
}
}
if ( _fired ) {
deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
}
}
return this;
},
// resolve with given context and args
resolveWith: function( context, args ) {
//console.info(context,context.promise);
if ( !cancelled && !fired && !firing ) {
firing = 1;
try {
while( callbacks[ 0 ] ) {
callbacks.shift().apply( context, args );
}
}
finally {
fired = [ context, args ];
firing = 0;
}
}
return this;
},
// resolve with this as context and given arguments
resolve: function() {
deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments );
return this;
},
// Has this deferred been resolved?
isResolved: function() {
return !!( firing || fired );
},
// Cancel
cancel: function() {
cancelled = 1;
callbacks = [];
return this;
}
};
return deferred;
},
[/javascript]
从源码中我们可以看到,$._Deferred函数返回一个deferred对象,包括
done,resolveWith,resolve,isResolved,cancel这5个方法,这个对象其实就是传说中的闭包,
这几个方法的作用分别是
done(arguments) 把要依次处理的函数(arguments)[可以是函数列表,也可以是函数数组]加入到队列表(callbacks)中,,返回 deferred 对象
resolveWith(context, args) 在给定的上下文(context默认为deferred=this对象)中执行队列,清除队列
resolve 执行队列,返回 deferred 对象
isResolved 队列是否执行完成
cancel 取消队列,返回 deferred 对象,队列被取消后,此$._Deferred函数生命周期结束
其中,done,resolve,resolveWith,cancel ,返回 deferred 对象,供链式调用.
以下我们为了方便理解,违背原则的直接使用 $._Deferred,
看实例:
[javascript]
function a(){console.info('函数a','此时执行环境是:',this,'可用参数有:',arguments);};
function b(){console.info('函数b','此时执行环境是:',this,'可用参数有:',arguments);};
var _testDefer = $._Deferred();
//加入并执行队列,在默认环境(context默认为deferred=this对象)中执行
_testDefer.done(a,b).resolve({a:'dd'},[1,2,3,4]);
//加入并执行队列,在环境(context此时为{a:'dd'})中执行
_testDefer.done(a,b).resolveWith({a:'dd'},[1,2,3,4]);
//多次加入调用执行队列,此时要注意,由于done函数的机制决定,第二次加入的队列直接用resolveWith在第一次的执行环境执行中执行掉,所以后续执行环境和参数不能改变,沿用第一次的.
_testDefer.done(a,b).resolve({a:'dd'},[1,2,3,4]).done([a,b]).done([a,b]);
//执行两次后,断裂...
_testDefer.done(a,b).resolve({a:'dd'},[1,2,3,4]).done([a,b]).cancel().done([a,b]);
[/javascript]
理解了$._Deferred 我们再来看 $.Deferred
源码:
[javascript]
// Full fledged deferred (two callbacks list)
Deferred: function( func ) {
var deferred = jQuery._Deferred(),
failDeferred = jQuery._Deferred(),
promise;
// Add errorDeferred methods, then and promise
jQuery.extend( deferred, {
then: function( doneCallbacks, failCallbacks ) {
deferred.done( doneCallbacks ).fail( failCallbacks );
return this;
},
fail: failDeferred.done,
rejectWith: failDeferred.resolveWith,
reject: failDeferred.resolve,
isRejected: failDeferred.isResolved,
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj , i ) {//internal
if ( obj == null ) {
if ( promise ) {
return promise;
}
promise = obj = {};
}
i = promiseMethods.length;
while( i-- ) {
obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ];
}
return obj;
}
} );
// Make sure only one callback list will be used
deferred.then( failDeferred.cancel, deferred.cancel );
// Unexpose cancel
delete deferred.cancel;
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
return deferred;
},
[/javascript]
$._Deferred 首先建立两个 _Deferred() 对象
var deferred = jQuery._Deferred(),
failDeferred = jQuery._Deferred(),
然后对 deferred 进行扩展:扩展了then,fail,rejectWith,reject,isRejected,最后是一个promise方法
再来看源码:
then: function( doneCallbacks, failCallbacks ) {
deferred.done( doneCallbacks ).fail( failCallbacks );
return this;
},
fail: failDeferred.done,
rejectWith: failDeferred.resolveWith,
reject: failDeferred.resolve,
isRejected: failDeferred.isResolved,
这么一处理等于是把 failDeferred 附加(拷贝)到 deferred 对象上去了,如果单单是为了拷贝直接一个对象就行了,但这里最主要的是为了控制对象体中的私有变量
fired,通过这个私有变量来确定对象体中的队列是否要执行,以下是一个简单的例子,看懂了,就算全明白了.
[javascript]
function a(who){
var fired;
return {
done: function(list){
if(fired){
console.log(who,fired,"被执行过了...",list);
}
return this;
},
resolve: function(){
fired = true;
return this;
}
}
}
function c(){
var aa = a('this is a'),
bb = a('this is b');
aa.fail = bb.done;
aa.reject = bb.resolve;
aa.then = function(){
aa.done(arguments[0]).fail(arguments[1]);
}
return aa;
};
//a 的 resolve执行,改变aa对象中的私有变量
c().done([7,8,9]).resolve().then([1,2,3],[4,5,6]);
//a 的 reject执行,改变bb对象中的私有变量
c().done([7,8,9]).reject().then([1,2,3],[4,5,6]);
[/javascript]
最后还有一个promise方法,jquery的Deferred方法为了实现多个函数队列之间的链式调用,添加了这么一个promise方法
从源码中我们可以知道,其实这个方法只返回一个包含 then done fail isResolved isRejected promise 这 6 个方法的对象
其中then是done,fail方法的合成体,也就是说then可以直接把done和fail方法写到一起,具体请看源码.
这样,一个队列执行完之后,只要返回这么一个对象,就可以用这个对象的,then,done,fail方法再把其它队列添加进去,根据isResolved和isRejected执行状态来确定具体执行那个队列分支,以此类推,可以一直这么执行下去......