当前位置: 代码迷 >> Web前端 >> jQuery1.7系列3: jQuery延迟列表(Deferred)
  详细解决方案

jQuery1.7系列3: jQuery延迟列表(Deferred)

热度:303   发布时间:2012-09-14 23:00:49.0
jQuery1.7系列三: jQuery延迟列表(Deferred)

?????????? 声明:写博客,是对自身知识的一种总结,也是一种分享,但由于作者本人水平有限,难免会有一些地方说得不对,还请大家友善? 指出,或致电:tianzhen.chen0509@gmail.com

???????????关注:国内开源jQuery-UI组件库:Operamasks-UI

?????? jQuery版本:v1.7.1

???????????????? jQuery1.7系列三:? jQuery延迟列表(Deferred)

一.函数列表

?

?????? 很多时候,我们想要执行一系列的函数,比如,当一个ajax请求后想要执行2个成功函数;当一个动画结束后想要执行2个函数,那么我们很容易想到用一个数组来承载这些待执行函数,如下所示:

var callbacks = [];

callbacks.push(function(){});

callbacks.push(function(){});

????? callbacks这个函数列表除了装载函数外,没有任何多余的控制能力,在很多时候我们需要更加智能的函数列表。比如,它可以控制不可以添加重复的函数;可以控制何时开始触发这些函数;可以控制当某个函数返回了false时后边的函数将不再执行……

?????? 如果拥有了这样强大的函数列表,那么很多不可思议的功能将可以轻松的实现,而这一切都可以在jQuery中找到踪迹。

?

二.jQuery加强的函数回调列表(Callbacks)

?

??????? jQuery1.7提供了一个加强的函数回调列表,它不仅仅用于存放函数,最重要的是它提供了更加复杂的控制,如何时才开始执行这些函数,这些函数是否可以重复。大体情况可以看下图:


jQuery.Callbacks便是它的实现,比如,看个最简单的代码:

<script>

    $(function($){

       var callbacks = $.Callbacks();

       callbacks.add(function(text){console.log("f1"+text);});

       callbacks.add(function(text){console.log("f2"+text);});

       //调用fire/fireWith才会执行这些函数,并且可以传递参数

       callbacks.fire("test");//输出结果: f1test f2test

    });

</script>

?

接下来,我们分别看下四个标准的控制标志。

1.????once

见名知义,只可以触发一次函数列表,也就是只有第一次调用fire/fireWith才会生效。

<script>

    $(function($){

       //注意传递了参数 "once"

       var callbacks = $.Callbacks("once");

       callbacks.add(function(){console.log("f1");});

       callbacks.fire();//输出 "f1"

       callbacks.fire();//什么也不发生

       callbacks.add(function(){console.log("f2");});

       callbacks.fire();//还是什么也不发生

 

       //默认为非"once"

       var callbacks = $.Callbacks();

       callbacks.add(function(){console.log("f3");});

       callbacks.fire();//输出f3

       callbacks.fire();//输出f3

    });

</script>

2.????memory

单看名字我觉得很难知道是什么意思,它的作用是在add一个函数的时候,如果这时候函数列表已经全部执行完了,那么刚刚add进去的函数会立即执行。

<script>

    $(function($){

       //注意传递了参数 "once"

       var callbacks = $.Callbacks("memory");

       callbacks.add(function(){console.log("f1");});

       callbacks.fire();//输出 "f1",这时函数列表已经执行完毕!

       callbacks.add(function(){console.log("f2");});//memory作用在这里,没有fire,一样有结果: f2

       callbacks.fire();//再触发一次,输出  f1 f2

 

       //与once一起使用

       callbacks = $.Callbacks("once memory");

       callbacks.add(function(){console.log("f3");});

       callbacks.fire();//输出 "f3",这时函数列表已经执行完毕!

       callbacks.add(function(){console.log("f4");});//没有fire,一样有结果: f4

       callbacks.fire();//由于为"once",这里将什么也不执行

    });

</script>

3.????unique

函数列表中的函数是否可以重复,这个也很简单。

?

<script>

    $(function($){

       var f1 = function(){console.log("f1");};

       var callbacks = $.Callbacks();

       callbacks.add(f1);

       callbacks.add(f1);

       callbacks.add(f1);

       callbacks.fire();//输出  f1 f1 f1

 

       //传递参数 "unique"

       callbacks = $.Callbacks("unique");

       callbacks.add(f1); //有效

       callbacks.add(f1); //添加不进去

       callbacks.add(f1); //添加不进去

       callbacks.fire();//输出: f1

    });

</script>

?

4.?????stopOnFalse

?????? 默认情况下,当fire函数列表的时候,整个列表里边所有函数都会一一执行,但如果设置了stopOnFalse,那么当某个函数返回了false时,后 边的函数将不再执行。即使设置了memory,再次添加的函数也不会执行了。但如果没有设置”once”,再次调用fire还是可以再次重新触发的。

<script>

    $(function($){

       var f1 = function(){console.log("f1"); return false};//注意 return false;

       var f2 = function(){console.log("f2");};

       var callbacks = $.Callbacks();

       callbacks.add(f1);

       callbacks.add(f2);

       callbacks.fire();//输出 f1 f2

 

       callbacks = $.Callbacks("memory stopOnFalse");

       callbacks.add(f1);

       callbacks.add(f2);

       callbacks.fire();//只输出  f1

 

       callbacks.add(function(){console.log("f3");}); //不会输出,memory已经失去作用了

       callbacks.fire();//重新触发,输出f1

    });

</script>

?

当然,Callbacks还提供了类似remove,disable,lock等方法,可以查看文档,就不一一介绍了,最主要的还是上边的核心思想以及四种控制其行为的状态标志位。

?

三.延迟队列(Deferred)

?

???????? Deferred是一个延迟队列,基于上边提到的Callbacks,其实Callbacks本身就已经拥有延迟功能了(还记得我们要自己fire才可以触发函数列表吧)。而Deferred内部包含了三个Callbacks,它们的定义分别如下:

var doneList = jQuery.Callbacks( "once memory" ),

        failList = jQuery.Callbacks( "once memory" ),

        progressList= jQuery.Callbacks( "memory" ),


可见,doneList和failList只可以触发一次,而progressList可以触发多次,而这个Deferred可以用来作啥?我们先来看段代码:

<script>

    $(function($){

       //xhr你可以认为就是一个Deferred

       var xhr = $.ajax({

           url : "test.html",

           //此success函数会添加到内部的doneList中去

           success : function(){

              console.log("success");

           },

           //此error函数会添加到内部的failList中去

           error : function(){

              console.log("error");

           }

       });

       xhr.success(function(){

           console.log("another success");

           //既然success添加到doneList中去,而它又具有"once memory"标识,所以这里添加进去的函数会马上执行

           xhr.success(function(){

              console.log("aftersuccess");

           });

       }).error(function(){

           console.log("anothererror");

       });

       //如果请求成功,输出结果将是  "success" "anothersuccess" "after success"

       //如果请求失败,输出结果将是 "error""another error"

    });

</script>

?

??????? 其实,Deferred的设计刚好用 在ajax上,在进行ajax请求的时候,我们往往要添加成功和失败的回调处理,而且有时并不止一个,所以内部用了doneList和failList两 个Callbacks,至于progressList是个比较特殊的东西,它相当于一个预处理的功能,在doneList和failList触发之前触 发,而且必须由用户自己去触发它,所以一般我们不用在意它。

?

?????? 那么有了Deferred,ajax请求怎么调用那些回调就显而易见了,只要在ajax请求后根据状态判断是成功还是失败,然后选择触发doneList或者failList就行了,没什么其它稀奇的事。

?

?????? 最 后再讲一下Deferred的promise方法,它是个挺好的概念来的,为了更好的理解它,我们回到上边的ajax实例。我们知道,doneList和 failList应该由ajax内部来进行触发,而你拥有的功能仅仅是加入函数而已,毕竟只有ajax自己才知道请求成功还是失败了,那如果你拿到了上边 的xhr变量后偏偏要自己去? xhr.fire(),不就可以破坏ajax的回调处理了?

?

????? 虽然我想作为一个正常的开发者没人会这样去做吧,但jQuery作者还是留了后招,上边虽然我注释到 xhr? 基本可以认为是一个Deferred,但严格上说并不完全是,它是调用了Deferred.promise()返回的一个弱Deferred,此 Deferred弱的地方就在于它没有fire/fireWith这样的方法,所以jQuery作者想得还是蛮周到的吧,当你想用Deferred开发自 己的东西时,也许也会利用到这一个方法的。

?

???? 总的说就是,Deferred.promise可以造出一个Deferred,但此Deferred只可以添加函数,不可以执行真正的触发。

?

四.最后的话

?

???????? jQuery的Deferred是一个设计挺巧妙的东西,如果你想实现自己的异步的队列,完全可以参考它的设计,关于所要掌握的一些概念,在上边都已经提 到了,这只是一个初端,但对你进一步去了解它我觉得还是有好处的,特别是如果你想研究它的代码实现,有了这些知识,就不会觉得非常难以理解了。

?

?

五. 附录: Callbacks源码注释

?

/*
 * Create a callback list using the following parameters:
 *
 *	flags:	an optional list of space-separated flags that will change how
 *			the callback list behaves
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible flags:
 *
 *	once:			will ensure the callback list can only be fired once (like a Deferred)
 *                  表示函数列表只有第一次fire()/fireWith()起作用
 *
 *	memory:			will keep track of previous values and will call any callback added
 *					after the list has been fired right away with the latest "memorized"
 *					values (like a Deferred)
 *                  在调用add()添加函数时,如果函数列表已经触发过并执行完毕,只要设置了此值,新添加进去的函数将马上执行,否则不执行
 *
 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
 *                  设置了此值表示函数列表中的函数不可以重复(两个不同引用指向同个函数意为重复)
 *
 *	stopOnFalse:	interrupt callings when a callback returns false
 *                  设置了此值,一旦某个函数返回了false,其后的函数将不会执行,但如果flags.once=false,那么再次
 *                  调用fire还是可以再次触发的
 *
 */
//flags的值比空白隔开 ,比  "once memory"  "unique stopOnFalse"
jQuery.Callbacks = function( flags ) {

	// Convert flags from String-formatted to Object-formatted
	// (we check in cache first)
	//最终flags将会是一个对象,比如{once:true , unique:true}
	flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};

	var // Actual callback list
		//存放函数的数组
		list = [],
		// Stack of fire calls for repeatable lists
		//如果flags.once=true,那么当你调用fire时,而且这时上一轮的fire还没有执行完毕,这时候将会把 [context , args]入栈
		//然后等上一次fire完结后,它会继续判断stack里边是否还有值,有的话会拿出[context , args]再次执行所有的函数列表
		stack = [],
		// Last fire value (for non-forgettable lists)
		//默认值为false,表示此函数列表还没有触发过,一旦此函数列表触发过了,将有两种可能的情况:
		//如果flags.once=true,那么memory=true,以后在fire时,一发现memory=true,将什么也不做
		//如果flags.memory=true,那么memory=[context , args]
		memory,
		// Flag to know if list is currently firing
		//是否正在触发中
		firing,
		// First callback to fire (used internally by add and fireWith)
		//从函数列表哪里开始执行
		firingStart,
		// End of the loop when firing
		//执行的函数列表长度,也就是执行函数的个数
		firingLength,
		// Index of currently firing callback (modified by remove if needed)
		firingIndex,
		// Add one or several callbacks to the list
		//真正执行添加函数的内部方法
		add = function( args ) {
			var i,
				length,
				elem,
				type,
				actual;
			for ( i = 0, length = args.length; i < length; i++ ) {
				elem = args[ i ];
				type = jQuery.type( elem );
				//传递了数组同样可以处理
				if ( type === "array" ) {
					// Inspect recursively
					add( elem );
				} else if ( type === "function" ) {
					// Add if not in unique mode and callback is not in
					//处理flags.unique,如果flags.unique=true,还要判断一下是否函数已经存在了
					if ( !flags.unique || !self.has( elem ) ) {
						list.push( elem );
					}
				}
			}
		},
		// Fire callbacks
		//真正执行触发函数列表的方法
		fire = function( context, args ) {
			args = args || [];
			//默认情况下,flags.memory=false,这时memory=true,表示已经执行过了
			//如果flags.memory=true,memory=[context , arsg]
			memory = !flags.memory || [ context, args ];
			firing = true;
			//下边为什么要这么麻烦呢,用三个参数来控制.
			//在add的时候,如果flags.memory=true,并且函数列表执行过了,这时候只要执行新的函数就行了,
			//而在add中会设置firingStart为合适的值,这样自然就只执行最后添加的函数了
			firingIndex = firingStart || 0;
			firingStart = 0;
			firingLength = list.length;
			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
				//这是唯一处理flags.stopOnFalse的地方
				if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
					//只要flags.stopOnFalse=true,并且函数返回了false,那么memory强制设为true,这时候flags.memory将会失去作用
					//因为flags.memory=true的作用就是 设置memory=[context , args]
					memory = true; // Mark as halted
					break;
				}
			}
			firing = false;
			//当触发执行完毕后,还要检查一下stack的情况,处理flags.once=false,并且调用了多次的fire()
			if ( list ) {
				if ( !flags.once ) {
					if ( stack && stack.length ) {
						memory = stack.shift();
						//memory=[context , args]在这里将用到了
						self.fireWith( memory[ 0 ], memory[ 1 ] );
					}
				} else if ( memory === true ) {
					//既然已经触发过了,并且没有额外的控制,如没有设置flags.once=false,没有设置flags.memory=true,那么作废该 Callbacks吧
					self.disable();
				} else {
					list = [];
				}
			}
		},
		// Actual Callbacks object
		//这就是调用 jQuery.Callbacks()返回的对象,用户眼中的Callbacks实例
		self = {
			// Add a callback or a collection of callbacks to the list
			add: function() {
			//只要未曾 disable此Callbacks,此条件将永远为true	
				if ( list ) {
					var length = list.length;
					add( arguments );//调用真正的函数添加处理
					// Do we need to add the callbacks to the
					// current firing batch?
					//如果当前Callbacks正在触发,那么只要修改firingLength的值就可以了,这样刚添加进去的函数自然就会执行了
					if ( firing ) {
						firingLength = list.length;
					// With memory, if we're not firing then
					// we should call right away, unless previous
					// firing was halted (stopOnFalse)
					//在添加的时候,只有一种情况会触发函数列表,那就是flags.memory=true,而且
					//如果flags.stopOnFalse=true,并且有函数返回了false,那么即使
					//flags.memory=true,这时候memory也会被强制设置为true,flags.memory完全失去了作用。
					} else if ( memory && memory !== true ) {
						firingStart = length;
						fire( memory[ 0 ], memory[ 1 ] );
					}
				}
				return this;
			},
			// Remove a callback from the list
			//有添加,自然也可以删除函数
			remove: function() {
				if ( list ) {
					var args = arguments,
						argIndex = 0,
						argLength = args.length;
					for ( ; argIndex < argLength ; argIndex++ ) {
						for ( var i = 0; i < list.length; i++ ) {
							//找到要删除的函数了,这时候要看Callbacks是否真在执行,若真在执行,要修改一些内部状态
							if ( args[ argIndex ] === list[ i ] ) {
								// Handle firingIndex and firingLength
								if ( firing ) {
									if ( i <= firingLength ) {
										firingLength--;
										//firingIndex表示正在执行函数的索引,如果
										//要删除的函数在这个索引前面,说明firingIndex必须减1,然后把前边那个函数删了
										if ( i <= firingIndex ) {
											firingIndex--;
										}
									}
								}
								// Remove the element
								list.splice( i--, 1 );
								// If we have some unicity property then
								// we only need to do this once
								//如果函数是唯一的,那么重新开始下一个函数的删除
								if ( flags.unique ) {
									break;
								}
							}
						}
					}
				}
				return this;
			},
			// Control if a given callback is in the list
			//一个给定函数是否已经在函数列表中了
			has: function( fn ) {
				if ( list ) {
					var i = 0,
						length = list.length;
					for ( ; i < length; i++ ) {
						if ( fn === list[ i ] ) {
							return true;
						}
					}
				}
				return false;
			},
			// Remove all callbacks from the list
			//纯粹的清空而已,不影响函数列表的添加和触发
			empty: function() {
				list = [];
				return this;
			},
			// Have the list do nothing anymore
			//一经disable,这个函数列表就完全不起作用了,相当于废了,可以休息了
			disable: function() {
				list = stack = memory = undefined;
				return this;
			},
			// Is it disabled?
			//判断函数列表是否已经disable了
			disabled: function() {
				return !list;//list==undefined就说明该函数列表废了
			},
			// Lock the list in its current state
			//一旦上了锁,stack=undefined,那么即使flags.once=false,调用再多的fire也是无济于事,其实Callbacks也相当于废了
			lock: function() {
				stack = undefined;
				if ( !memory || memory === true ) {
					self.disable();
				}
				return this;
			},
			// Is it locked?
			locked: function() {
				return !stack;
			},
			// Call all callbacks with the given context and arguments
			fireWith: function( context, args ) {
				if ( stack ) {
					if ( firing ) {
						if ( !flags.once ) {
							//如果当前Callbacks正在触发,你这时又调用了一次fire,这时候只要flags.once=false,那么就会把[context , args]入栈
							//这样当上一次的触发完成时,将会检查stack,然后入栈自动重新执行函数列表
							stack.push( [ context, args ] );
						}
					} else if ( !( flags.once && memory ) ) {
						//把条件转换成   !flags.once || !memory感觉好懂一些
						//如果flags.once=false,那么继续触发吧
						//或者flags.once=true,但 memory=false,表示还没触发过,那么执行第一次的触发吧
						fire( context, args );
					}
				}
				return this;
			},
			// Call all the callbacks with the given arguments
			fire: function() {
				self.fireWith( this, arguments );
				return this;
			},
			// To know if the callbacks have already been called at least once
			//memory=false表示还没有触发,其它值均表示触发过了
			fired: function() {
				return !!memory;
			}
		};

	return self;
};

?

?

  相关解决方案