简介:
由于jQuery.Defferred本身是基于jQuery.Callback的,所以,需要对jQuery.Callback有一定地了解才行。
Deferred相关功能,用于延迟对象,实现对异步的统一管理(其实,本质还是Callback)。代码框架如下:
jQuery.extend({
Deferred: function( func ) {
},
when: function(subordinate){
}
});
可以看出就是为jQuery添加了Deferred、when两个属性方法。通过调用这两个方式实现对异步的统一管理。
代码详解:
Deferred:
变量:
可以从源码中看出来,Deferred声明了tuples、state、promise、deferred这四个变量
var tuples = [
[ "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() {
},
then: function() {
},
promise: function( obj ) {
}
},
deferred = {};
- tuples:一个二维数组,元素是数组,元素数组内的元素组成是,字符串、字符串、jQuery.Callback对象、可能存在的字符串(前两个元素数组存在第四个元素)。
- state:字符串,"pending"。
- promise:一个对象,内部初始化了四个属性,都是函数。
- state函数直接返回state字符串
- promise函数,传入一个对象,如果存在的话,就调用jQuery.extend,将promise的各个属性赋值给对象。不存在的话,就直接返回promise。
- always、then:涉及到之后的函数,先不讲。
- deferred:一个没有属性的空对象。
之后将promise的then属性赋值给promise的pipe属性。(可以延长函数地执行,和then内部的执行有关)
promise.pipe = promise.then
jQuery.each:
使用jQuery.each对二维数组tuples进行遍历操作。i是数组下标,tuple是二维数组内的元素数组。
jQuery.each( tuples, function( i, tuple ) {
});
创建了两个变量,list是jQuery.Callbacks()返回的对象,stateString是代表状态的字符串。
var
list = tuple[ 2 ],
stateString = tuple[ 3 ];
可以看出,promise.done、promise.fail、promise.progress这三个函数,本质上还是jQuery.Callbacks中的add函数。
promise[ tuple[1] ] = list.add;
如果stateString存在的话(即数组有第四个元素),为list添加三个函数,
- 执行的时候将state改为stateString(即tuple[3])
- 将另外一个的callback销毁,(list = stack = memory = undefined)(因为存在第四个元素的数组只有两个,而0 ^ 1=1,1 ^ 1=0)
- 将第三个的callback锁住。(因为是有memory的,所以只是将stack设为undefined)
if ( stateString ) {
list.add(function() {
state = stateString;
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
这段代码中的大致作用就是,当第一个和第二的数组中的callback执行(fire())的时候,将另外一个的callback销毁,同时锁住第三个数组的callback。
为deferred添加属性:
deferred的resolve、reject、notify属性,就是一个函数,就是对应地执行resolveWith、rejectWith、notifyWith,并且传入参数this(会根据不同的条件改变,很简单,就不细说了)和arguments。
而对应地resolveWith、rejectWith、notifyWith,本是上就是为对应的callback调用fireWith。
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 );
}
将promise地对象,全部复制给defered。
如果使用jQuery.Deferred地时候,传入了函数元素,就执行该函数,this是deferred,参数也是deferred。
之前的promise.always、promise.then
由之前的代码,可以知道,Deferred其实就是内部创建了三个jQuery.Callbacks,根据不同的属性方法进行触发,(resolve、done一组、reject、fail一组、notify、progress一组,都对应callback的fire和add)。
promise.always:
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
always,使用done和fail添加函数(两个callback中的list都有了这个函数),说明可以由resolve、reject任意一个触发。
(因为callback中的add是返回this的,在这里就是deffered,所以执行一个函数之后,直接再通过属性继续执行)
promise.then:
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();
},
- 将参数赋值给fns变量。
- 使用jQuery.Deferred,传入一个函数(其实就就是执行了结尾的func.call),其中函数的this和参数newDefer就都会是deferred。
- 然后使用jQuery.each遍历tuples,i是下标,tuple是元素。action是代表执行的字符串,fn则是传入的参数数组中下标与i对应的元素(在这里可以知道,传入then的参数函数,第一个与resolve组对于,第二个与reject组对应,第三个与notify组对应)
- 然后就是调用deferred,使用对应的tuple[1](done、fail、progress,本质上都是add),添加函数到对应的callback中的lst中。
- 添加的函数内部,首先创建returned变量,returned接收参数fn或者是fn.apply( this, arguments )的执行结果(then中的函数被触发后,就在这里执行了。不过,arguments用来接收触发的时候传进来的数组)
- 这里的代码内容用于pipe了(
很少用,可以不管,我也比较迷糊,只是大致地理解了一下),如果returned存在,且returned.promise是函数的话,就使用done、fail、progress加载函数;不过不是的话,之前的函数的返回结果可以作为参数会传递到新的函数中。 - 将fns赋值给null,释放内存。
- 最好执行promise(),由于没有参数传入,就会返回promise。
when:
Deferred是基于callback之上的,when则是基于Deferred之上的,是对多个Deferred进行管理,可以大致的理解为,使用Deferred管理多个Deferred。
when中的Deferred都触发过了,才会执行。
变量:
var i = 0,
resolveValues = core_slice.call( arguments ),
length = resolveValues.length,
// the count of uncompleted subordinates
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
// Update function for both resolve and progress values
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
},
progressValues, progressContexts, resolveContexts;
resolveValues:代表参数的数组(core_slice.call( arguments )相当于arguments.slice(),将参数数组这个类数组,彻底传转化为数组)。
length:输入参数的数量。
remaining:当输入的参数只有一个的且不为函数或者不存在的话,返回0,其他情况返回数组长度本身。功能是计数器,用于计算传进来的Deferred还有几个没被触发。
deferred:当remaining的长度为一的时,返回参数subordinate,反之则返回jQuery.Deferred()。功能是用于管理传入的多个Deferred的Deferred。
updateFunc:函数,接受三个参数,i是执行函数的下标,contexts是代表作用域的合集(很明显,在这里可以看到,只是做个样子,表示一下,其实还是this),values代表被管理的Deferred合集数组
返回一个函数,接收参数value。开始先将contexts[ i ]赋值this,values[ i ]赋值为value,如果value长度大于一,就转化数组。如果values等于progressValues(这是个空数组,而在后面加载时,传入的也是progressValues),就执行notifyWith;反之,如果remaining为1的话,执行触发resolveWith(是1的,算上这一次,就相当于都执行过了)。
progressValues、progressContexts、resolveContexts:创建还没初始化。
触发的判断:
// add listeners to Deferred subordinates; treat others as resolved
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
.done( updateFunc( i, resolveContexts, resolveValues ) )
.fail( deferred.reject )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
}
// if we're not waiting on anything, resolve the master
if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
}
遍历,根据条件resolveValues,将deferred的参数加载到每个被管理的Deferred:
- done:配合updateFunc,表示只有全部都resolve,才会触发when的deferred的notifyWith
- fail:只要reject过一次,就会触发when的deferred的reject
- progress:配合updateFunc,只要执行了notify,就会触发when的deferred的resolveWith
最后,如果 !remaining 判定过了(表示之前就已经执行过了)
小结:
jQuery2.0.3源码本身具有一定复杂性,以及我自身的限制,还做不到一目了然很简单地表述出来,所以,本文有一定地障碍和门槛,可能会有些费劲。
这个只是根据其中的一些情况分析解读(因为我也有点烦躁了,所以不是很全面详细),离真正的掌握,还是有点距离的。
源码:3043行--------3183行
jQuery.extend({
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;
},
// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
var i = 0,
resolveValues = core_slice.call( arguments ),
length = resolveValues.length,
// the count of uncompleted subordinates
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
// Update function for both resolve and progress values
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
},
progressValues, progressContexts, resolveContexts;
// add listeners to Deferred subordinates; treat others as resolved
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
.done( updateFunc( i, resolveContexts, resolveValues ) )
.fail( deferred.reject )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
}
// if we're not waiting on anything, resolve the master
if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
}
return deferred.promise();
}
});