jQuery._Deferred 理解

沈俊晤
2023-12-01

本系列文章都是基于jquery1.6.4的,请参照源代码。

今天,我们来研究jQuery._Deferred方法。和往常一样,先放源代码。

jQuery.extend({
	// Create a simple deferred (one callbacks list)
	_Deferred: function() {
		var // callbacks list
			callbacks = [],
			// stored [ context , args ]
			fired,
			// to avoid firing when already doing so
			firing,
			// flag to know if the deferred has been cancelled
			cancelled,
			// the deferred itself
			deferred  = {

				// done( f1, f2, ...)
				done: function() {
					if ( !cancelled ) {
						var args = arguments,
							i,
							length,
							elem,
							type,
							_fired;
						if ( fired ) {
							_fired = fired;
							fired = 0;
						}
						for ( i = 0, length = args.length; i < length; i++ ) {
							elem = args[ i ];
							type = jQuery.type( elem );
							if ( type === "array" ) {
								deferred.done.apply( deferred, elem );
							} else if ( type === "function" ) {
								callbacks.push( elem );
							}
						}
						if ( _fired ) {
							deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
						}
					}
					return this;
				},

				// resolve with given context and args
				resolveWith: function( context, args ) {
					if ( !cancelled && !fired && !firing ) {
						// make sure args are available (#8421)
						args = args || [];
						firing = 1;
						try {
							while( callbacks[ 0 ] ) {
								callbacks.shift().apply( context, args );
							}
						}
						finally {
							fired = [ context, args ];
							firing = 0;
						}
					}
					return this;
				},

				// resolve with this as context and given arguments
				resolve: function() {
					deferred.resolveWith( this, arguments );
					return this;
				},

				// Has this deferred been resolved?
				isResolved: function() {
					return !!( firing || fired );
				},

				// Cancel
				cancel: function() {
					cancelled = 1;
					callbacks = [];
					return this;
				}
			};

		return deferred;
	},
         //下面是Deferred方法和when方法
        Deferred: function( func ) {},
        when: function( firstParam ) {}
)};
jQuery._Deferred方法是通过jQuery.extend方法加入到jQuery中的,关于jQuery.extend方法,请查看我的另外一篇博文(现在还没有写)。

从第5-13我们可以看出_Deferred函数对象拥有5个属性,分别是callbacks(数组),fired,firing,cancelled,deferred(对象)。

callbacks数组是存放_Deferred对象通过done方法注册的函数,这时候我们可以根据第34行和52行这两条语句,我们可以知道callbacks模拟了队列,因此通过done注册的回调函数,触发时回调函数是根据传入的顺序来执行的。

fired属性表示_Deferred对象是否已经是满足状态,根据第56行,我们可知道此时fired的值得形式是[context,args] (后面解释),而未满足状态的fired是undefined或者0。

firing=1(第50行)表示_Deferred对象正在从未满足状态转换到满足状态,防止_Deferred对象多次被resolve(第46行),当_Deferred对象为满足状态时,firing=0(第57行)。

cancelled属性表示_Deferred对象是否被取消,也就是是否可以注册回调函数,对于被取消的_Derferred对象是不可以触发和注册回调函数的。deferred对象是在_Deferred函数内定义的,最终被返回,实际_Defered对象就是指的deferred对象,这样是为了保护前面的四个变量;

deferred对象有done,resolve,resolveWith,isResolved,cancel这些函数。现在我们来分别看这5个函数,从最简单的开始:

cancel函数只有三句(76-78),把cancelled设为1,清空callback数组,被调用了cancelled的_Deferred对象是不可被触发的。

isResolved函数是判断_Deferred对象处于什么样的状态,当_Deferred对象处于满足状态(fired=[context,args])或者是正在从未满足状态到满足状态的转换过程中都是true,其他为false,这儿(第71行)有两个是为了在firing和fired都是undefined的时候返回true。

resolve函数可以传入参数,并把this(通常就是deferred对象)和传递进来的参数arguments传递给resolveWith函数。当触发是,实际真正处理的resolveWith函数(第65行)。下面我们来看看resolveWith函数。

resolveWith的函数的第一句(第46行):说明只有没有取消的并且_Deferred对象是未满足的才可以触发回调函数。第49行把firing设为1表示_Deferred对象正在从未满足状态转换为满足状态,而第51句的while循环和52行的callbacks.shift().apply(context,args),通过done注册的回调函数安装注册的顺序被触发。

下面是本篇文章的最重要的部分,done函数的分析。done函数的参数可以是函数,或者是元素是函数的数组;我们一句一句来分析done函数,第18-23行定义了六个变量,其中_fired变量是为了保存fired变量的值,防止fired变量被修改引起的副作用;我们知道_Defered对象是满足状态的时候,通过done注册的函数是会被立即执行的。这可以从第37-39知道,因为_Deferred对象是刚刚是满足的(第26行有fired=0),那么if(_fired)就会为真,然后会调用resolveWith方法;如果此时_Defered是正在转换的,那么第28-36行的回调函数已经被注册到了callbacks,那么现在通过done注册的回调函数也会被立即执行的(因为有另外一个resolveWith调用还没有完成);那我们现在来仔细看看注册回调函数的部分。

for循环对done的每一个参数进行处理,在for循环内,type表示了参数的类型,如果是数组(第31行),那么通过在进行一步的deferred.done.apply( deferred, elem )调用把数组内的函数(可以嵌套数组(语法来讲,当然我们也不会这么折腾自己),还有数组具有length属性,因此对于数组参数在进行done(这个时候只有一个数组参数)调用,可以再次通过for循环,把内部的函数注册到callbacks);如果type是函数那么直接调用callbacks.push,把回调函数放到callbacks的尾端。保证了回调函数的调用顺序。


今天的_Deferred理解就到这里了,第一次写博客,语言组织不太习惯,还请大家指点。


 类似资料: