本来不出意外应该写jQuery的ajax模块的,不过。。。
不错,废话我都懒得多打一句,直接讲when方法:
when: function(subordinate) {
var i = 0,
resolveValues = slice.call(arguments),
length = resolveValues.length,
remaining = length !== 1 || (subordinate && jQuery.isFunction(subordinate.promise)) ? length : 0,
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
updateFunc = function(i, contexts, values) {
return function(value) {
contexts[i] = this;
values[i] = arguments.length > 1 ? slice.call(arguments) : value;
if (values === progressValues) {
deferred.notifyWith(contexts, values)
} else if (!(--remaining)) {
deferred.resolveWith(contexts, values)
}
}
},
progressValues, progressContexts, resolveContexts;
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 (!remaining) {
deferred.resolveWith(resolveContexts, resolveValues)
}
return deferred.promise()
}
嗯,紧凑了一点,将就着看。
首先要搞清楚promise对象和deferred对象之间的联系(可参考这篇博客),关键点在于:promise对象表示承诺会去执行通过done、fail、progress接口添加的回调函数,而deferred对象同样具有添加回调函数的接口(实际调用的就是promise对象的接口),不过deferred对象主要是用于改变promise对象的状态(说白了就是使用reject、resolve、notify调用回调函数列表——下一次讲ajax模块的时候再把这个例子的链接加上)。
所以结论就是想要使用promise对象就得需要一个deferred对象。不信你就看when方法的代码:
// 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(),
the master Deferred,要不是当只有一个参数时,使用该参数,否则就新创建一个Deferred对象。
if ( length > 1 ) {
//...
}
if ( !remaining ) {
//...
}
只有参数长度大于1才会进行update状态的操作。
再来看看jQuery是如何把一组延迟对象的执行作用域和对应参数给传递到回调函数里面的:
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;
}
}
上面说了,只有多于一个延迟对象等待执行的时候才会进行状态操作。
jQuery预创建了参数的数组、progress的执行环境数组、resolve的执行环境数组(两个数组用于区分是执行resolve还是progress),而对于reject就是简单的给添加一个调用reject回调列表的函数。
for循环中给每个延迟对象的done、progress回调列表中添加一个回调函数,此处使用的是updateFunc函数创建闭包来构建该回调函数的执行作用域及参数,然后添加到回调列表中。
再看updateFunc方法:
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
if ( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !(--remaining) ) {
deferred.resolveWith( contexts, values );
}
};
}
而对于values数组,每次将传入的参数放入到values[i]里面,然后判断values===progressValues从而得知是进行执行notify(即:progress接口添加的回调列表)还是resolve(即:done接口添加的回调列表)。对于progress是每次都会执行,而resolve只会执行一次(可参见$.Callback的once选项)(每次收到resolve的通知后会递减remaining,当remaining为0时,表明所有延迟对象执行结束)。
其实说来想获得所有延迟对象的执行结束的通知不难,在具备一个观察者模式下(jQuery使用底层为Callback封装的Deferred对象),只需在监听对应状态的回调函数列表的最后添加一个函数去改变剩余的延迟对象数就行了,难点在于怎么去保存每个回调函数的参数、作用域这些东西。
jQuery的技巧不可谓不巧妙,实在高明。
有其他疑问或错误之处欢迎之处~