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);
上述分析仅为自己根据源码以及应用进行分析,如果表述有误或有不对地方 还请大家不吝赐教!!!