$.when( { testing: 123 } ).done(function( x ) { alert( x.testing ); // Alerts "123" });测试代码2:
$.when().then(function( x ) { alert( "I fired immediately" ); });测试代码3:
var d1 = $.Deferred(); var d2 = $.Deferred(); $.when( d1, d2 ).done(function ( v1, v2 ) { alert( v1 ); // "Fish" alert( v2 ); // "Pizza" }); d1.resolve( "Fish" ); d2.resolve( "Pizza" );测试代码4:
var d1 = $.Deferred(); var d2 = $.Deferred(); var d3 = $.Deferred(); $.when( d1, d2, d3 ).done(function ( v1, v2, v3 ) { alert( v1 ); // v1 is undefined alert( v2 ); // v2 is "abc" alert( v3 ); // v3 is an array [ 1, 2, 3, 4, 5 ] }); d1.resolve(); d2.resolve( "abc" ); d3.resolve( 1, 2, 3, 4, 5 )测试代码5:
$.when( $.ajax( "/page1.php" ), $.ajax( "/page2.php" ) ).done(function( a1, a2 ) { var data = a1[ 0 ] + a2[ 0 ]; // a1[ 0 ] = "Whip", a2[ 0 ] = " It" //a1和a2是page1和page2的ajax请求时候调用resolve时候的参数,每一个参数是一个数组,数组签名是[data,statusText,jqXHR] if ( /Whip It/.test( data ) ) { alert( "We got what we came for!" ); } }); //下面的调用方式表示,如果两者都成功那么调用myFunc函数,否则调用myFailure函数 $.when( $.ajax( "/page1.php" ), $.ajax( "/page2.php" ) ) .then( myFunc, myFailure );
原文链接:点击打开链接
测试用例1(把数组传入其它函数用于修改):
var arr=[undefined,undefined]; func1(arr); func2(arr); alert("outcome->"+arr);//打印[Fish,Pizza] function func1(array) { array[0]="Fish"; } function func2(array) { array[1]="Pizza"; }note:在when函数的源码中就用到了用数组对象来判断是notify还是resolve调用类型的方式!
测试用例2:
$.when1(d1, d2).progress(function(val){alert("progress"+val)}); d1.notify("aa");//打印"progressaa" d2.notify("bb");//打印"progressaa"note:notifyWith在Callbacks对象里面是用memory构造的,所以会记住上一次的参数结果,当第一次notify("aa")那么aa会被记忆, 下次再次notify的时候还是用旧的参数调用新的函数!在 Callbacks里面不是说过,会经过两个步骤吗,第一个步骤是用旧参数调用新函数,第二个步骤是用新参数调用所有的函数?解释:请不要把Callbacks和when方法混淆,虽然他们底层思想是一样的!这里牵涉到的Callbacks只有progress对应的Callbacks,他会记忆上一次调用的参数没有错,理解的很对,但是他的参数是when给的,在里面调用deferred.notifyWith( contexts, values );其中values是一个数组,元素1是第一个Deferred的参数,元素2是第二个Deferred调用时候的参数,如果把代码修改成为下面这样:(要用形参去接收)
var d1 = $.Deferred(); var d2 = $.Deferred(); //第一种情况:resolve调用 $.when1(d1, d2).progress(function(val,val2){alert(val2+"progress"+val)}); d1.notify("aa");//undefinedprogressaa d2.notify("bb");//bbprogressaa
$.when源码分析:
when: function( subordinate /* , ..., subordinateN */ ) { var i = 0, resolveValues = 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 ) { //这里的value就是resolve等方法传入的参数!如果调用的是done方法,那么就会把resolveContext传递过来 //这个context里面会放入when方法传入的参数,也就是多个Deferred对象 contexts[ i ] = this; values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; //除非外面调用的notify否则不可能相等,因为只有progress会把progressValue传递进来,从而数组引用相同,否则不可能相等! //如果不相等就表示调用的是resolve,如果相等那么就表示调用的是notify!这种用数组相等来判断是那种调用的方法极其巧妙! if ( values === progressValues ) { deferred.notifyWith( contexts, values ); //如果不想等那么表示是resolve那么把remiaining减少,如果没有减少到0,那么就不会调用外层的Deferred对象的resolveWith方法 //只有减少为0的时候才会调用外层的resolveWith方法! } else if ( !(--remaining) ) { //(1)这里就可以说明上面的测试代码4的情况,为什么每一个Deferred对象resolve的参数参数会被传递到总的Deferred对象的done //方法或者progress方法中,因为所有Deferred对象的回调函数updateFunc()会把参数传递到values[i]中,把每次传递的一个参数 //作为这个数组的一个元素,于是最后这个数组就会存在,当总的Deferred已经resolve,也就是remaining是0的时候就会 //调用他,底层调用的是Callbacks对象的fireWith //(2)至于上面的if语句,每一个Deferred对象调用notify那么if语句总是满足的,于是都会调用notifyWith.同时要记住: //notifyWith在Callbacks对象里面是用memory构造的,所以会记住上一次的参数结果,当第一次notify("aa")那么aa会被记忆 //下次再次notify的时候还是用旧的参数调用新的函数! deferred.resolveWith( contexts, values ); } }; }, progressValues, progressContexts, resolveContexts; // add listeners to Deferred subordinates; treat others as resolved if ( length > 1 ) { //length是几,那么创建的数组长度就是几! //有什么作用? //(1)在updateFunc用数组的引用判断是那种调用类型,对于notify类型,那么调用progress那么传入的参数是progressContexts, //progressValues于是在updateFunc中values就是progressValues,也就是两者相等那么表示要调用总的Deferred对象的progress方法! //于是调用nodifyWith方法,上下文就是Deferred对象,第二个参数就是values[i]也就是notify函数调用时候传递的参数,如dfd.notify("xxx") //那么values[i]="xxx",这个"xxx"会传入到when函数的progress方法中作为参数! //(2)在updateFunc中会用这些数组保存所有的notify或者resolve或者reject调用时候的参数,从而把这个参数数组封装到done,fail,progress回调函数中! progressValues = new Array( length ); progressContexts = new Array( length ); resolveContexts = new Array( length ); for ( ; i < length; i++ ) { //如果不是Deferred对象那么直接把remaining长度减一! if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { resolveValues[ i ].promise() //resolveValues[ i ].promise()就是arguments[0]或者arguments[1] //done就是arguments[i]的done方法!也就是给这个arguments[i]对应的Callbacks中添加一个回调函数,当这个when参数,也就是传入的Deferred对象 //resolve以后就会回调这个函数,回调这个函数就是去执行updateFunc函数,在这个函数里面对remaining进行修改! .done( updateFunc( i, resolveContexts, resolveValues ) ) //只要有一个未完成,那么就触发新创建的deferred的reject方法,进而调用when后面的fail方法! .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(); }
总结:
(1)相比于Deferred对象,该when方法可以运用于多个延迟对象!
(2)when方法的参数必须是延迟对象,如果不是延迟对象那么when函数会直接跳过这个函数,不会判断它成功与否!其它的函数成功他就成功!
所以如果参数不是Defered对象那么唯一的作用就是会把when里面的参数作为参数传递给done函数,done函数中通过arguments[0],arguments[1]访问!(3)当没有传入参数的时候那么这时候在内部就会创建一个Deferred对象,然后remaining是0,调用该创建的对象的resolveWith方法!进而调用done方法!
(4)传入一个参数的时候这个deferred就是第一个参数,如果第一个参数resolve那么就会马上调用done方法,因为公用了同一个deferred对象!
(5)如果传入的参数不是Deferred那么创建一个新的Deferred对象然后remianing是0,于是调用该deferred的resolveWith,同时传入resolveValues数组,也就是会把数组传入done方法!
(6)要知道源码在for循环中用到了done(updateFunc(i,resolveContexts,resolveValues))其中updateFunc调用后其实返回的是一个匿名函数,该函数会接受一个参数。其实在done方法中调用的jQuery.Callbacks的add方法,也就是往这个Callbacks中添加了函数,构成了一个list数组,然后在resolve的时候其实是调用了Callbacks对象的fireWith,在fire中传入的参数会被原封不动的用于去调用list中的所有的函数,这里的逻辑就是一样的,会拿着fire中的参数去调用done中的所有的函数,所以updateFunc返回的匿名函数的参数value就是resolve调用时候的参数值!
(7)contexts[ i ] = this;其中的this就是Deferred对象,可以通过打印this.done等知道!至于为什么是Deferred对象,原因就是done方法最后还是Deferred对象调用的,所以this就是指向Deferred对象!