jQuery的Deferred对象测试笔记以及源码分析


 官方地址见: 点击打开链接
 测试代码1:(deferred.always)
always参数可以是一个function对象也可以是一个function数组,当Deferred对象resolve或者reject那么该函数会被调用。因为deferred.always()方法返回一个Deferred对象,所以其它的Deferred的方法可以继续调用,也包括继续调用always方法,当Deferred已经reject或者resolve,那么回调函数按照我们添加的顺序被调用,调用参数就是我们提供给reject,resolve,rejectWith,resolveWith的参数
因为$.get方法返回对象是jqXHR对象,他来自于Deferred对象,于是我们可以用deferred.always()来添加成功和失败的回调函数!
$.get( "test.php" ).always(function() {
  alert( "$.get completed with success or error callback arguments" );
});
测试代码2:(deferred.done)
done函数的参数可以是function也可以是function数组,当Deferred已经resolve那么就会被调用,调用的顺序就是我们添加的顺序,该方法也是返回Deferred对象,所以适用于Deferred的方法可以链式调用,包括另一个done方法。当Deferred被resolved,那么done方法被调用,调用的参数就是传递给resolve或者resolveWith的参数。
因为get方法返回jqXHR对象,所以我们直接可以用done方法添加成功回调函数.
$.get( "test.php" ).done(function() {
  alert( "$.get succeeded" );
});
function fn1() {
  $( "p" ).append( " 1 " );
}
function fn2() {
  $( "p" ).append( " 2 " );
}
function fn3( n ) {
  $( "p" ).append( n + " 3 " + n );
}
//创建deferred对象
var dfd = $.Deferred();
dfd.done().done( [ fn1, fn2 ], fn3, [ fn2, fn1 ] ).done(function( n ) {
    $( "p" ).append( n + " we're done." );
  });
//点击按钮时候Deferred调用resolve方法!打印结果[1 2 and 3 and 2 1 and we're done.]
$( "button" ).on( "click", function() {
  dfd.resolve( "and" );
});
测试代码3:(deferred.fail)
和done方法一致

测试代码4:(deferred.isRejected)
jQuery1.7中已经过时,用deferred.state代替,如果deferred在reject状态返回true。也就是调用了reject或者rejectWith方法的同时fail方法已经完成

测试代码5:(deferred.isResolved)
Query1.7中已经过时,用deferred.state代替,如果deferred在resolved状态返回true.也就是调用了resolve或者resolveWith方法的同时done方法已经完成
deferred有三种状态,pending,reject,resolved.如果你要将deferred状态改为resolved,那么你就要判断当前的状态!

测试代码6:(deferred.notify)

只有Deferred的构建者能够调用该方法,你可以阻止其它的代码修改Deferred的状态,或者通过deferred.promise()返回一个严格状态的Promise对象来记录状态。如果
notify被调用了,那么所有通过deferred.then,deferrred.progess添加的回调都会被调用,每一个回调函数都会传入notify的参数,任何在resolved或者rejected
状态以后调用的notify都会被忽略

测试代码7:(deferred.notifyWith[context,args])
只有Deferred的构建者能够调用该方法,你可以阻止其它的代码修改Deferred的状态,或者通过deferred.promise()返回一个严格状态的Promise对象来记录状态。当notifyWith
被调用,那么所有通过deferred.then,deferrred.progess添加的回调都会被调用,每一个回调函数都会传入notifyWith的参数,任何在resolved或者rejected
状态以后调用的notify都会被忽略

测试代码8:deferred.pipe( [doneFilter ] [, failFilter ] )或者deferred.pipe( [doneFilter ] [, failFilter ] [, progressFilter ] )
 jQuery 1.8以后pipe方法已经过时,用deferred.then代替。 
 //过滤resolve值
var defer = $.Deferred(),
//pipe方法
  filtered = defer.pipe(function( value ) {
    return value * 2;
  });
 //传入参数5
defer.resolve( 5 );
filtered.done(function( value ) {
  alert( "Value is ( 2*5 = ) 10: " + value );
});
//过滤reject值
var defer = $.Deferred(),
//传入两个参数,第二个是failFilter
  filtered = defer.pipe( null, function( value ) {
    return value * 3;
  });
defer.reject( 6 );
//这里是filtered.fail事件,上面是done事件
filtered.fail(function( value ) {
  alert( "Value is ( 3*6 = ) 18: " + value );
});
//链式任务
var request = $.ajax( url, { dataType: "json" } ),
  chained = request.pipe(function( data ) {
    return $.ajax( url2, { data: { user: data.userId } } );
  });
chained.done(function( data ) {
  // data retrieved from url2 as provided by the first request
});
测试代码9(deferred.progress( progressCallbacks [, progressCallbacks ] )产生progress事件后回调函数)
当Deferred产生notify或者notifyWith事件时候回调函数,当已经resolve或者reject那么progress不会被调用。当处于resolve或者reject状态时候,任何新添加的progress函数会马上执行!

测试代码10:(deferred.promise)
该方法允许异步函数,从而不打断现在代码的执行。promise对象仅仅暴露了Deferred的一些方法用于添加额外事件或者判断状态,如then, done, fail, always, pipe, progress, state and promise
没有提供改变状态的方法如:(resolve, reject, notify, resolveWith, rejectWith, and notifyWith)。如果提供了参数,那么就会把这些方法封装到参数上面,并且返回,这可以把promise的行为添加到一些已经存在的对象上面.
function asyncEvent() {
  var dfd = jQuery.Deferred();
  // Resolve after a random interval
  setTimeout(function() {
    dfd.resolve( "hurray" );
  }, Math.floor( 400 + Math.random() * 2000 ) );
 
  // Reject after a random interval
  setTimeout(function() {
    dfd.reject( "sorry" );
  }, Math.floor( 400 + Math.random() * 2000 ) );
 
  // Show a "working..." message every half-second
  setTimeout(function working() {
    if ( dfd.state() === "pending" ) {
alert("pending");
      dfd.notify( "working... " );
      setTimeout( working, 500 );
    }
  }, 1 );
  // Return the Promise so caller can't change the Deferred
  //返回一个Promise,因此调用者无法在外面改变Deferred对象的状态!
  return dfd.promise();
} 
// Attach a done, fail, and progress handler for the asyncEvent
//为异步事件添加done,fail,progress事件!
$.when( asyncEvent() ).then(
  function( status ) {
    alert( status + ", things are going well" );
  },
  function( status ) {
    alert( status + ", you fail this time" );
  },
  function( status ) {
    $( "body" ).append( status );
  }
);
//已经存在的obj对象
var obj = {
    hello: function( name ) {
      alert( "Hello " + name );
    }
  },
  //创建Deferred对象
  defer = $.Deferred();
// 使得obj具有promise的方法
defer.promise( obj );
//改变状态为resolved
defer.resolve( "John" );
//将obj当作Promise使用
obj.done(function( name ) {
  obj.hello( name ); //"Hello John"。将resolve的参数传入done函数
}).hello( "Karl" ); //"Hello Karl"。该hello方法是这个promise独有的方法
测试代码11:(deferred.reject)
只有Deferred的构建者能够调用该方法,你可以阻止其它的代码修改Deferred的状态,或者通过deferred.promise()返回一个严格状态的Promise对象来记录状态。如果状态变为reject,那么通过
then或者fail添加的方法都会被调用。传入的参数就是调用reject时候传入的参数,如果当Deferred处于reject状态时候添加的失败回调都会马上执行

测试代码12:(deferred.rejectWith)
只有Deferred的构建者能够调用该方法,你可以阻止其它的代码修改Deferred的状态,或者通过deferred.promise()返回一个严格状态的Promise对象来记录状态。如果状态变为reject,那么通过
then或者fail添加的方法都会被调用。传入的参数就是调用rejectWith时候传入的参数,如果当Deferred处于reject状态时候添加的失败回调都会马上执行

测试代码13:(deferred.resolve)
只有Deferred的构建者能够调用该方法,你可以阻止其它的代码修改Deferred的状态,或者通过deferred.promise()返回一个严格状态的Promise对象来记录状态。如果状态变为resolve,那么通过
then或者done添加的方法都会被调用。传入的参数就是调用resolve时候传入的参数,如果当Deferred处于reject状态时候添加的失败回调都会马上执行

测试代码14:(deferred.resolveWith)

只有Deferred的构建者能够调用该方法,你可以阻止其它的代码修改Deferred的状态,或者通过deferred.promise()返回一个严格状态的Promise对象来记录状态。如果状态变为resolve,那么通过
then或者done添加的方法都会被调用。传入的参数就是调用resolveWith时候传入的参数,如果当Deferred处于reject状态时候添加的失败回调都会马上执行

测试代码15:(deferred.state)
具有三种状态:
pending状态,非reslove和reject
resolve状态:resolve或者resolveWith被调用,同时done方法被调用
reject状态:reject或者rejectWith被调用,同时fail方法被调用

测试代码16:(deferred.then( doneCallbacks, failCallbacks [, progressCallbacks ] ))
jQuery1.8以后,then方法返回一个promise对象可以对deferred的状态和值用函数进行过滤,代替过去的pipe方法
//过滤resolve
var filterResolve = function() {
  var defer = $.Deferred(),
    filtered = defer.then(function( value ) {
      return value * 2;
    });
  defer.resolve( 5 );
  filtered.done(function( value ) {
    $( "p" ).html( "Value is ( 2*5 = ) 10: " + value );//打印10
  });
};
//过滤reject
var defer = $.Deferred(),
  filtered = defer.then( null, function( value ) {//fail
    return value * 3;
  });
defer.reject( 6 );
filtered.fail(function( value ) {
  alert( "Value is ( 3*6 = ) 18: " + value );
});
//链式调用
var request = $.ajax( url, { dataType: "json" } ),
  chained = request.then(function( data ) {
    return $.ajax( url2, { data: { user: data.userId } } );
  });
 
chained.done(function( data ) {
  // data retrieved from url2 as provided by the first request
});
测试代码17:(jQuery.Deferred( [beforeStart ] ))


测试代码18:(.promise( [type ] [, target ] ))

var div = $( "<div>" );
div.promise().done(function( arg1 ) {
  // 马上调用,打印"true"
  alert( this === div && arg1 === div );
});
//当所有的动画完成以后,返回的Promise变成resolved状态,包括那些一开始就存在于callback集合中的和那些后来添加的
$( "button" ).on( "click", function() {
  $( "p" ).append( "Started..." );
  $( "div" ).each(function( i ) {
    $( this ).fadeIn().fadeOut( 1000 * ( i + 1 ) );
  }); 
  $( "div" ).promise().done(function() {
    $( "p" ).append( " Finished! " );
  });
});
//用$.when()将返回的Pormise对象变成resolved,.promise可以用于jQuery集合
var effect = function() {
  return $( "div" ).fadeIn( 800 ).delay( 1200 ).fadeOut();
}; 
$( "button" ).on( "click", function() {
  $( "p" ).append( " Started... " );
 
  $.when( effect() ).done(function() {
    $( "p" ).append( " Finished! " );
  });
});

下面我们开始jQuery是怎么完成的,请提前阅读Callbacks相关问题

源码问题1:测试每一个Deferred对应的三个Callbacks作用

                             var dfd=$.Deferred();
				setInterval(function()
				{
				   alert(111);
				   dfd.resolve();
				 },1000)
				dfd.done(function(){
				 alert("成功");
				}).fail(function()
				{
				  alert("失败");
				}).progress(function(){alert("进行中!")})
note:这个例子done中的函数只会执行一次,以后在调用resolve的时候不会触发done方法,reject也是一样!但是如果把上面修改为notify那么一直会交替弹出:[111,进行中]。为什么完成和未完成只能触发一次,而进行中要连续触发?因为成功或者失败是一种状态,然而进行中可以连续触发,这对进度条等有作用!之所以能够实现只会调用一次,因为源码中用的是jQuery.Callbacks("once memory"), 因为once+memory的组合调用了一次就会把list=[]所以下次继续resolve(底层调用fireWith),list已经为空了!没有效果!
源码问题2:(memory在这里有什么作用,复习Callbacks内容)

                          var cb=$.Callbacks("memory");
				 cb.add(function(){
				   alert(1);
				});
				cb.fire();//点击按钮以后会把新的函数添加入cb中间去,而memory表示不需要自己调用就能执行
				//因为add里面会自己执行!会拿着上一次的参数执行,上一次如果没有参数那么就是undefined!
				$("input").click(function()
				{
				   cb.add(function()
				   {
				     alert(2);
				   })
				});
note:到了这里你应该有了想法了,memory如果没有手动调用的时候会自己调用!那么在上面的例子中,如果我们已经resolve了,也就是list集合中的函数已经全部调用了,那么我们现在重新添加一个done方法会怎么样呢? 立即执行!之所以会这样就是memory记住了上一次的参数,不用手动调用而会立即调用新的函数!(这是为什么我总结:如果没有手动调用,在有memory的情况的下只会用老参数调用新函数;如果手动调用的时候才会发生两个阶段,阶段一就是用老参数调用新函数,阶段二就是用新函数调用所有的函数)这是为什么resolve以后再次添加done方法这个方法会立即执行的原因!这和下面的例子是一样的!
源码问题3:

                            var dfd=$.Deferred();
				setTimeout(function()
				{
				   alert(111);
				   dfd.resolve();
				 },1000)
				dfd.done(function(){
				 alert("aaa");
				}).fail(function()
				{
				  alert("失败");
				}).progress(function(){alert("进行中!")})
                           
				$("input").click(function()
				{
				   cb.add(function()
				   {
				     alert("bbb");
				   })
				});
//点击的时候状态已经完成了,也就是已经resolve了,当点击按钮的时候马上弹出bbb!只要状态已经完成,那么你再进行done操作的时候会立即执行,
//这就是memory起的作用!
源码问题4:(简单执行)
  function aaa()
       {
         var dfd=$.Deferred();
	 setTimeout(function()
	 {
	   dfd.resolve();
	 },1000)
       }
	var newDfd=aaa();
	newDfd.done(function(){alert("成功")}).fail(function(){alert("失败");})
   // 很显然会弹出"成功",因为resolve会调用底层的fireWith,fireWith就会把集合中的函数全部执行,而resolve对应的Callbacks就是done!
测试问题4:(Deferred对象的状态很容易被修改!)
       function aaa()
        {
         var dfd=$.Deferred();
	 setTimeout(function()
	 {
	 
	   dfd.resolve();
	 },1000)
	 return dfd;
       }
	var newDfd=aaa();
	newDfd.done(function(){alert("成功")}).fail(function(){alert("失败");})
	newDfd.reject();//上面的定时器还没有执行,失败已经触发了,这时候成功不会调用!说明状态很容易修改掉
测试问题5:(为什么源码中的要封装promise对象,该对象没有resolve,resolveWith等修改状态的方法)
    function aaa()
       {
         var dfd=$.Deferred();
	 setTimeout(function()
	 {
	 
	   dfd.resolve();
	 },1000)
	 return dfd.promise();
       }
	var newDfd=aaa();
	newDfd.done(function(){alert("成功")}).fail(function(){alert("失败");})
	newDfd.reject();//这时候不能修改状态,还会报错,因为promise对象没有reject!
测试问题6:(pipe方法的使用)
                 var dfd=$.Deferred();
		    setTimeout(function()
		    {
		      dfd.resolve("hi");
		    },1000)
		  //返回一个新的延迟对象!返回值作为延迟对象的resove的参数
		 var newDfd= dfd.pipe(function(){
		       return arguments[0]+"xxx"
		  })
                 //这个会立即执行,因为上面已经resolve了!
		  newDfd.done(function(){
		    alert(arguments[0]);
		  });
jQuery.Deferred对象源码:

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;
				},
		//不管成功还是失败都是会回调的!也就是把这个函数同时添加如done对应的Callbacks对象中也添加进fail对于的Callbacks对象中
				//记住这里的done,fail对应于相应的Callbacks的add方法!
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				//分开写,第一个是完成的回调,第二个是失败,第三个是进度
				then: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;
					//pipe方法和then方式是同一个方法,所以返回值是一个Defered类型,参见博客中“测试问题6”
					//jQuery.Deferred中传入一个函数表示立即执行!
					return jQuery.Deferred(function( newDefer ) {
						jQuery.each( tuples, function( i, tuple ) {
							//假如传入了三个函数,那么第一次i为0,获取到第一个函数也就是成功回调函数
							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
							// deferred[ done | fail | progress ] for forwarding actions to newDefer
							//第一次调用done函数,第二次是fail,第三次是notify函数
			//deferred[ tuple[1] ]就是deferred["done"]这就是add方法,也就是把后面的那个函数添加到done里面去执行!
							deferred[ tuple[1] ](function() {
			 //获取第一个成功回调函数的返回值,这里的arguments就是传递给resolve或者reject或者notify的参数!
					//之所以arguments是resolve的参数是因为:这里直接在done的Callbacks列表中加入了这个匿名函数,
								//如果外面已经resolve了那么这里就会立即执行!
								var returned = fn && fn.apply( this, arguments );
								//如果返回值是promise的处理逻辑
								if ( returned && jQuery.isFunction( returned.promise ) ) {
									returned.promise()
										.done( newDefer.resolve )
										.fail( newDefer.reject )
										.progress( newDefer.notify );
								} else {
									//否则:
			newDefer[ tuple[ 0 ] + "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
				//如果没有参数那么返回Deferred对象对应的promise,如果有参数那么就是让参数继承promise所有的方法
				//后面用到这里让Deferred继承promise的所有的方法!
				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里面封装了三个Callbacks对象,promise的done|faile|progress对应于相应对象的add方法
			//done里面的函数会被封装到jQuery.Callbacks("once memory")这个Callbacks对象上面,
			//fail会被封装到jQuery.Callbacks("once memory")这个Callbacks对象上面,
			//progress会被封装到jQuery.Callbacks("memory")这个Callbacks对象上面,而且这三个Callbacks是互不影响的!
			//也就是add时候只是添加到特定的Callbacks里面!
			// promise[ done | fail | progress ] = list.add
			promise[ tuple[1] ] = list.add;
			// Handle state
			//触发了未完成那么不能触发完成,触发了完成不可能触发为完成的代码,这里就是用来控制的!
			//第一次是resolve,所以tupes[i^1]就是reject,也就是resolve执行了不能再执行reject,即让所有的reject全部调用disable!
			//这里要深入理解Callbacks才行,原理为:
			//(1)如果是resolve类型那么我把这个reject中的集合jQuery.Callbacks("once memory")全部调用disable
			//也就说这个集合里面的list=stack=memory=undefined!(请仔细阅读Callbacks源码)
			//(2)而且这个函数是放在最前面的,也就是每一次开始调用的时候他都是最前调用的,以防有人调用reject改变状态!
			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;
			};
			//(1)Deferred对象的resolveWidth等方法都是对应于Callbacks对象的fireWith方法!
			//而且Deferred对象的resolve,reject,notify方法调用的是resolveWith,rejectWith,notifyWidth
			//也就是归根到底,resolve方法还是调用fireWidth只是要传进去相应的参数而已!
			//从上面的代码可以看到,如果调用resolveWith的调用者是Deferred对象,那么调用fireWith时候传入的第一个参数就是promise
	//如果调用resolveWidth的不是Deferred对象,那么调用fireWidth时候的第一个参数就是调用者,第二个参数是resolveWith传入的参数!
//(2)这里也就是说对于resolveWith来说,他对应于jQuery.Callbacks("once memory")的fireWith也就说resolve调用了只会调用这里面的函数的队列
//而这里面函数的队列对应于就是done中间的队列,done中间的函数队列又对应于jQuery.Callbacks("once memory")中add的方法!
			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;
	}

总结:

(1)Deferred对象有三个方法done,fail,notify,他们对应于不同的Callbacks对象的add方法,所以我们在这三个方法里面直接添加函数!如果是done方法相当于直接在done对于的Callbacks中添加了回调函数,构建出一个list集合!那么如何触发这个集合中的函数呢?Deferred对象专门为这个Callbacks准备了resolve,resolveWith等方法(底层调用该Callbacks的fireWith方法),该方法相当于执行通过done方法添加的Callbacks中的所有的函数!(其它函数也是同样的思路!)

(2)回调函数在promise方法上面,但是状态改变的方法全部在deferred对象上!Deferred只是多了修改状态的方法!
(3)如果调用resolveWith的调用者是Deferred对象,那么调用fireWith时候传入的第一个参数就是promise, 如果调用resolveWidth的不是Deferred对象,那么调用fireWidth时候的第一个参数就是调用者,第二个参数是resolveWith传入的参数!

智能推荐

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告