首页 > 代码库 > jq Deferred的源码实现
jq Deferred的源码实现
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)
})
可以看出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(); } } return this; }, // Call all the callbacks with the given arguments fire: function() { self.fireWith( this, arguments ); return this; },
,,,,然后看一下jq callbacks的几个用法
var cb = $.Callbacks(‘memory‘);
cb.add(function(a){
console.log(a,‘第一次‘)
});
cb.fire(‘hello world‘);
cb.add(function(a){
console.log(a,‘第二次‘)
});
当使用memory时在fire调用后使用add,回调函数就会直接被调用,而callbacks还有‘once‘,‘locked‘的用法,具体可以去看一下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来自于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,notify方法就是类似callbacks add方法,可以看最之前的例子
接下来看看内部deferred,由于这些方法都类似,所以就拿done来作为例子 ,在之前的
jQuery.each( tuples, function( i, tuple ) {})过后,那么done本身就有了4个回调函数
fn1用于修改状态,表示在这个回调函数执行后的状态
fn2,fn3用于关闭队列和告知函数队列已不在执行
fn4 是添加一个新建callbacks的fire对象,(这点在讲then时很重要,看看之前callbacks的4点,我说过执行函数队列时时对象冒充的,具体下面讲)
- 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 = http://www.mamicode.com/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 ); } },
上面大概就是这样,话理的其实不是很清楚,不知道下次来看会不会蒙圈,有问题欢迎指出
jq Deferred的源码实现