Deferred的实现主要是依靠jq的callbaks方法的,他是对callbacks的封装,先来看看callbacks的一段小代码
var cb = $.Callbacks(); cb.add(function(a){ console.log(a) }); cb.fire('hello world')// 输出a值,hello world;
cb.add(function(a){
console.log(a) //没有fire的所以不会输出,如果$.Callbacks('memory')就会直接输出了,可以看看callbacks用法
})
可以看出cb在调用fire方法时就会执行回调函数,那么他是怎么实现的呢,其实是这样的,callbacks里面有一个数组list用来存储回调函数,而Callbacks通过闭包来返回一个包装了add,fire这一些方法的对象,并且由于闭包的关系数组list不会被处理,那么add方法就是向list里推入回调函数的,最后fire时只要顺序执行下list里的函数就好了,有兴趣的可以去看一下jq的源码,总之,有4点需要得出
- Point
1 ,这个函数队列里的每一个函数都是靠对象冒充实现的,
2 ,callback.fire() 和 callback.fireWidth()都是可以实现便厉队列的
3 ,callback.fire()的this的来自上下文(表达有些问题,在这是为了区分和fireWith的区别)
4 ,callback.firWith的第一个参数是可以自己定义的,不一定是来自于上下文
看看源码区别下
fireWith: function( context, args ) { if ( !locked ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; queue.push( args ); if ( !firing ) {
//这个fire是callbacks函数的主要逻辑方法,可以看看下图 fire(); } } return this; }, // Call all the callbacks with the given arguments
//两个方法其实就是一样的,只不过fireWith更具体,部分逻辑在这里
fire: function() { self.fireWith( this, arguments ); return this; },
fire = function() { // Enforce single-firing locked = options.once; fired = firing = true;for ( ; queue.length; firingIndex = -1 ) { memory = queue.shift(); while ( ++firingIndex < list.length ) { if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && options.stopOnFalse ) { console.log(options.stopOnFalse) // Jump to end and forget the data so .add doesn't re-fire firingIndex = list.length; memory = false; } } } // Forget the data if we're done with it if ( !options.memory ) { memory = false; } firing = false; if ( locked ) { if ( memory ) { list = []; } else { list = ""; } } },
,,,,然后看一下jq callbacks的几个用法
var cb = $.Callbacks('memory'); cb.add(function(a){ console.log(a,'第一次') }); cb.fire('hello world'); cb.add(function(a){ console.log(a,'第二次') });
var callbacks = $.Callbacks('once'); callbacks.add(function() { console.log('第一个函数'); }) callbacks.add(function() { console.log('第二个函数'); }) callbacks.fire(); //输出 '第一个函数','第二个函数' callbacks.fire(); //未执行
当使用memory时在fire调用后使用add,回调函数就会直接被调用,而用once参数时在fire就会只输出一次(实质就是once的参数会告诉callbacks每次fire后都将把函数数列清除),,而callbacks还有'unique
','stopOnFalse
'的这一些可以添加的参数,具体可以去看一下callbacks,不具体说callbacks,,事实上看明白可callbacks就大概可以理解deferred是怎么回事了,假如我是这么写
- Point
function easyDef(){ var Def = $.Callbacks('memory once'), def = { done:Def.add, fire:Def.fire, fireWith:Def.fireWith } return def; } function defTime(arg){ var def = easyDef(),that = this; setTimeout(function(){ //由于内部时使用apply调用的,所以必须用 []包起来 def.fireWith(this,['接收到远方的消息:你好!!!']) },3000); return def; } defTime('向远方发送消息').done(function(data){ console.log(data,this) //接收到远方的消息:你好!!! Window → http://127.0.0.1:8020/newChat/test/deferr.html //可以看到this来自于上面fireWidth传入的this 也就是window,好像例子举得有点废话,, })
可以看出一个简单的deferred就实现了,当然真实的不会这么简单,还要考虑很多,下面看看deferred,,,
Deferred拥有三种状况,也就是进程中,成功时,失败后,jq先用数组存储了需要使用的三种状态
jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], stateString = tuple[ 5 ]; promise[ tuple[ 1 ] ] = list.add; if ( stateString ) { list.add( function() { state = stateString; }, tuples[ 3 - i ][ 2 ].disable, tuples[ 0 ][ 2 ].lock ); } list.add( tuple[ 3 ].fire ); deferred[ tuple[ 0 ] ] = function() { deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); return this; }; deferred[ tuple[ 0 ] + "With" ] = list.fireWith; } );
使用jq的each方法,来重写封装callbacks的方法,最后的得到内部的deferred对象,(在这里需要说一下通过$.Deferred()产生的def对象启示是由两个对象组成的,之后通过promise的promise对象方法将内部deferred对象和promise对象组合成了一个终极deferred对象,为了区分,这个deferred对象就叫做内部deferred对象好了,看不明白看下面的图)可以看一下dererred对象到底是怎样的,首先看一下所谓的两个对象
//Deferred的promise对象和deferred对象,使用promise对象的promise方法扩展deferred对象方法,其实就是jq的extends方法
//扩展deferred,返回最终的deferred对象 promise.promise( deferred );
promise = { state: function() { return state; }, always: function() { deferred.done( arguments ).fail( arguments ); return this; }, "catch": function( fn ) { return promise.then( null, fn ); }, // Keep pipe for back-compat pipe: function( /* fnDone, fnFail, fnProgress */ ) { /* */ }, then: function( onFulfilled, onRejected, onProgress ) { /* */ }, promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise; } }, deferred = {};
上图就是返回的deferred最终的对象,拥有许多方法,done,fail,progress方法就是类似callbacks add方法,可以看最之前的例子
接下来看看内部deferred,由于这些方法都类似,所以就拿done来作为例子 ,在之前的
jQuery.each( tuples, function( i, tuple ) {})过后,那么done本身就有了4个回调函数
fn1用于修改状态,表示在这个回调函数执行后的状态
fn2,fn3用于关闭队列和告知函数队列已不在执行
fn4 是添加一个新建callbacks的fire对象,(这点在讲then时很重要,看看之前callbacks的4点,我说过执行函数队列时时对象冒充的,具体下面讲)
然后还可以知道的是,deferred的方法
done,fail,progress方法统统是用来加入函数数列的方法,他们不会一下子执行,而是在被通知方法通知后才会执行,并且它们的参数来自于
通知方法的参数,以下是状态通知方法
resolve,resolveWith 表示成功,reject,rejectWith表示失败,notify,notifyWith表示过程,
有点乱,那么举个例子,小明去面试后他并不能马上知道自己通过了没,他就会等面试官来告诉他,面试官一直没有回复,那么就相当于面试官传递出来的信息是notify,还在过程中,
那么小明的状态就是等,小明很害羞,不会自己去问,而面试官告诉他,你通过了后,面试官就发送了信息resolve,小明就会做他之前想好的事,果不其然,几天后就来上班了,也就是小明.done(function(){小明上班或者别的}),而面试官说不好意思,你不符合我们的条件,你不用来了,面试官就发送了信息reject,表示拒绝了,那么小明就是,小明.fail(function(){暂时失业}),从中可以看出,面试官才不管小明你要不要来上班,反正他就只是通知你,你过没有过,所以通过后小明所做的事却不是由面试官能决定的,小明完全遵照自己的意愿办事
- Point
var defo = $.Deferred();
defo.done(function(data){
console.log(data)
//Object { a: 1, b: 2, c: 3 }
})
defo.resolveWith(this,[{a:1,b:2,c:3}])
//当执行了resolveWith方法时,就会执行队列里的函数,
//遵照上面写的4个fn,加上上面done的一个,所以一共是5个fn将被执行,
//由于jQuery.Callbacks( "once memory" )是打开了memory和once的,所以执行过后,函数队列将被清除,下一次done会直接执行,
//并且回调的参数将一直沿用resolveWith所传参数,可以联想到 jq ajax的def实现,
defo.done(function(data){
console.log(data)
//Object { a: 1, b: 2, c: 3 }
})
$.ajax({
type:"get",
url:"1.json",
async:true
}).done(function(data){console.log(data)})
.done(function(data){
///$.ajax()返回的即使def对象 第二次使用done将不在去请求,因为之前已经成功过了
console.log(data)
});
大致的deferred的源码实现就是这样了,其他还有他的一些方法,看看then的方法是如何实现的,我感觉是比较绕,,不记下来就有点乱,这才是我真正想记下来的目的,其他容易记,这个太绕了,首先看看then的用法
- Point
-
timeDef = function(target) { var def = $.Deferred() setTimeout(function() { def.resolveWith(def, ['向' + target + '发出问候,问候被接受']); }, 3000) //def.promise返回的是promise对象,该对象只有done,fail等加入函数队列类方法 //,没有resolve等执行方法,可以避免外界去执行这个def对象(外界执行def.resolve()就会让def失去意义); return def.promise(); }; var hi = timeDef('/小明'). then(function(data) { console.log(data) // 向/小明发出问候,问候被接受 return timeDef('/小刚'); }). done(function(data) { //向/小刚发出问候,问候被接受 console.log(data) }) hi.done(function(data) { console.log('状态:' + this.state() + ',你已' + data + '') }). then(function(data){ console.log('状态:' + this.state() + ',你已' + data + '') }). then(function(data){ console.log(data) //结果为undefined,因为之前没有返回def对象,过程被关闭, //that = undefined; //args = [ returned ]; //上面是源码的话,意思就是,this = undefined;data = undefined })
then就是对def对象的过程式执行,,只要then的回调return def对象时,就会开启准备 执行下一个 def对象 then可以传入三个参数then(fn,fn,fn),
分别是成功,失败,过程中的回调函数,调用then后会 对为之前的def对象(倘若之前也是then并且then的回调返回了def对象,那么就处理这个返回的对象,如果此回调没有返回对象,那么表示def对象过程已经结束了,继续then已经没有用了,参见上面的代码),
下面这是部分的then源码
jQuery.Deferred( function( newDefer ) { tuples[ 0 ][ 3 ].add( resolve( 0, newDefer, jQuery.isFunction( onProgress ) ? onProgress : Identity, newDefer.notifyWith ) ); tuples[ 1 ][ 3 ].add( resolve( 0, newDefer, jQuery.isFunction( onFulfilled ) ? onFulfilled : Identity ) ); tuples[ 2 ][ 3 ].add( resolve( 0, newDefer, jQuery.isFunction( onRejected ) ? onRejected : Thrower ) ); } ).promise();
可以看到 它给tuples的一个callbacks添加了队列函数, 我暂时称它为then_callback,下面也是这样,可以参照上面的图,再来看一下吧
所以添加的是fn4,fire方法,所以当xx.then的xx执行好了后,就会执行xx的fn1,fn2,..函数,而fn4被执行后(也就是fire()被执行)所以就会执行then_callbacks,而then方法被执行后就是在已在then_callbacks推入了队列函数,所以将开始执行队列函数,可见下面,以下是源码,部分已被我部分删掉,resolve执行的回调
function resolve( depth, deferred, handler, special ) { return function() { var that = this, args = arguments, mightThrow = function() { var returned, then; if ( depth < maxDepth ) { return; } //对象冒充执行 args就是数据 returned = handler.apply( that, args ); //如果有returned 就是说then的回调函数有return def对象 then = returned && ( typeof returned === "object" || typeof returned === "function" ) && returned.then; //then是否为函数 if ( jQuery.isFunction( then ) ) { //对progress的处理 if ( special ) { then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ) ); } else { maxDepth++; //为return的def对象添加了then_callbacks的函数队列 then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ), resolve( maxDepth, deferred, Identity, deferred.notifyWith ) ); } // Handle all other returned values } else {
//then没有返回return if ( handler !== Identity ) { that = undefined; args = [ returned ]; } //当return的def对象执行了后,把参数,上下文传给本then,使其可以继续有过程 //a.then(return def) def执行, a.then返回的一个def被执行,并且参数来自def ( special || deferred.resolveWith )( that, args ); } },
上面大概就是这样,话理的其实不是很清楚,不知道下次来看会不会蒙圈,有问题欢迎指出