jquery源码之延迟对象--Deferred

说到延迟对象deferred就不得不提,为什么会需要它,它用来干嘛的。

浏览器环境与后端的node.js一直存在着各种消耗巨大或阻塞线程的行为,但前端是不应该因此而阻塞的,所以基于javascript 的单线程机制,他的一种解耦的方式就是提供异步API。

何为异步API?

简单来说,它是不会立即执行的方法。例如浏览器提供的setTimeout和setInterval。后面再出现各种事件回调。再之后,就更多,XMLHttpRequest, postMessage 等等

这些东西有一个共同点,就是都拥有一个回调函数,用来描述接下来要干什么。

在业务复杂的情况下,就下造成各种回调嵌套的情况,以及随之而来的复杂代码。以致很难管理代码。

于是 Promise规范就出现了,然后jQuery.deferred就出现了。(Promise规范可以去geogle自行了解)

首先jQuery.defferred是基于jQuery.Callbacks实现的。这个待会再提,

首先我们得看看jQuery.defferred能给大家带来怎样的便利吧。

 

// 假设我们有多个异步行为

// 且我们需要在一个异步行为的基础上执行另一个异步行为

// 那我们应该怎么实现呢



function asynFn1(callback) {

	setTimeout(function() {

		console.log("complete 1");

		callback();

	}, 1000);

}

function asynFn2(callback) {

	setTimeout(function() {

		console.log("complete 2");

		callback();	

	}, 1000);

}

function asynFn3(callback) {

	setTimeout(function() {

		console.log("complete 3");

		callback();

	}, 1000);

}



// 如果业务复杂的话,就不是这么简单的触发了

asynFn1(function() {

	asynFn2();

	asynFn3();

});

 

  

 

 

功能是实现了,但实现的很繁琐,维护起来也很麻烦。如果我们用jQuery.deferred呢。

// 首先我们封装异步对象

function asynFn1() {

	var deferred = $.deferred();

	// 当前还可以对延迟对象,比如说失败了应该怎么样

	setTimeout(function() {

		console.log("complete 1");

		// 触发真正的触发事件

		deferred.resolve();

	}, 1000);

	// promise 只提供done,fail,notity等添加触发后方法的接口,

	// 它不是提供直接触发事件的方法的

	// 隐藏信息

	return deferred.promise();

}

// 业务复杂的情况也会是优雅的链式调用

asynFn1().done(asynFn2).done(asynFn3);

  是不是瞬间高大上一点了呢。deferred用处很多,我一个小菜可能就想不了那么远了,还是看看我们的源码吧。

// 源码是通过extend扩展的,即直接把deferred对象放在jQuery的命名空间下

// 我们可以通过 jQuery.Deferred 或者 $.Deferred直接访问

jQuery.extend({ 

	// 从参数命名来看,Deferred希望获得一个 function作参数

	Deferred: function( func ) {

		// 定义一个元组

		// 看这里有jQuery.Callbacks

		// 其实它的内部核心实现就是依赖Callbacks的

		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") ]

			],

			// 初始状态为pending

			state = "pending",

			// 一个神奇的对象,他是用来暴露给外部的,

			// 主要负责注册监听器

			promise = {

				//返回当前状态, 状态有 resolved, rejected, pending

				state: function() {

					return state;

				},

				// 无论成功失败都需要执行的监听者

				always: function() {

					deferred.done( arguments ).fail( arguments );

					return this;

				},

				// 一次性注册多个监听方法,顺序为 done, fail, progress

				/*

					从浅显的角度看,类似于

					function(d, f, g) {

						if (d) {this.done(d)};

						if (f) {this.fail(d)};

						if (g) {this.progress(d)};

					}

					但jQuery就实现的比较深了

				*/

				// 最终返回新deferred的promise对象

				then: function( /* fnDone, fnFail, fnProgress */ ) {

					// 首先保存参数

					var fns = arguments;

					// 构建一个新的Deferred对象,并传递了一个函数做参数

					// 这里在构建结束前,有这么多一段代码

					/*

						// Call given func if any

						if ( func ) {

							// 以当前deferred调用并传递参数为当前deferred

							func.call( deferred, deferred );

						}

					*/

					return jQuery.Deferred(function( newDefer ) {

						// 所以到这, newDefer指向的是新构建的deferred

						// each 遍历tuples

						jQuery.each( tuples, function( i, tuple ) {

							// 获取动作, reject, resolve, notify

							var action = tuple[ 0 ],

							// 获取刚刚保存的指定索引的fn

								fn = fns[ i ];

							// deferred[ done | fail | progress ] for forwarding actions to newDefer

							// 将新的deferred的触发事件绑定在旧的deferred对象的想对应的监听器上

							deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?

								function() {		

									var returned = fn.apply( this, arguments );

									// 如果返回值是个deferred对象

									if ( returned && jQuery.isFunction( returned.promise ) ) {

										// 返回延迟对象即不直接触发,

										// 而是讲newDefer的触发事件绑定在改延迟对象的监听器上

										// 意思是等待该延迟对象触发再触发新延迟对象绑定的监听器

										returned.promise()

											.done( newDefer.resolve )

											.fail( newDefer.reject )

											.progress( newDefer.notify );

									} else {

									    // 否则直接触发新的deferred的触发事件

										// 注意这里用的是返回值作参数

										// 通道实现

										newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );

									}

								} :

								// 将新的deferred的触发事件绑定在旧的deferred对象的想对应的监听器上

								newDefer[ action ]

							);

						});

						// 清空fns

						fns = null;

					}).promise();

				},

				// Get a promise for this deferred

				// If obj is provided, the promise aspect is added to the object

				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 ];



			// done, fail, progress 监听器添加交给promise

			// promise[ done | fail | progress ] = list.add

			promise[ tuple[1] ] = list.add;



			// Handle state

			if ( stateString ) {

				// 如果有状态,

				// 则添加修改状态,禁用另个状态的功能,

				// 并锁住notify

				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对象上

			// 相当于这边是提供给服务端程序员

			// 而promise是提供给客户端程序员

			// deferred[ resolve | reject | notify ] = list.fire

			deferred[ tuple[0] ] = list.fire;

			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;

	}

});

 

  

 

  还有一种异步情况:

 

 

// 假设我们有多个异步行为

// 而我们需要在这些异步行为都完成的情况下做一些额外的动作

// 那我们应该怎么实现呢

// 看例题吧

function asynFn1(callback) {

	setTimeout(function() {

		console.log("complete 1");

		callback();

	}, 1000);

}

function asynFn2(callback) {

	setTimeout(function() {

		console.log("complete 2");

		callback();	

	}, 1000);

}

function asynFn3(callback) {

	setTimeout(function() {

		console.log("complete 3");

		callback();

	}, 1000);

}



// 那么怎么在监听到这三个异步方法都执行完的行为呢

// 貌似只能有到函数委托了

var proxy = function(callback) {

	var successCount = 0;

	return function() {

		successCount++;

		var args = [].slice.call(arguments);

		if (successCount === 3) {

			callback.apply(null, args);

		}

	};

}



var pFn = proxy(function () { // 注册目标方法

	console.log("i have nothing to wait");

});



// 将异步方法执行时绑定在代理后的目标方法

asynFn1(pFn);

asynFn2(pFn);

asynFn3(pFn);

/*

complete 1 

complete 2 

complete 3 

i have nothing to wait

*/





var proxy2 = function(callback) {

	var successCount = 0;

	function add() {

		var asynFns = [].slice.call(arguments);

		successCount = asynFns.length;

		for (var fn in asynFns) {

			asynFns[fn](done);

		}

	}

	function done() {

		successCount--;

		if(!successCount) {

			callback();

		}

	}

	return add;

}



var pFn2 = proxy2(function() { // 注册目标方法

	console.log("i have nothing to wait 2");

});



// 将异步方法交于代理方法处理

pFn2(asynFn1, asynFn2, asynFn3);

/*

complete 1 

complete 2 

complete 3 

i have nothing to wait 2 

*/

 

 

 

于是又出现了一个when方法

// Deferred helper

	when: function( subordinate /* , ..., subordinateN */ ) {

		var i = 0,

			resolveValues = core_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 ) {

					contexts[ i ] = this;

					values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;

					if( values === progressValues ) {

						deferred.notifyWith( contexts, values );

					} else if ( !( --remaining ) ) {

						deferred.resolveWith( contexts, values );

					}

				};

			},



			progressValues, progressContexts, resolveContexts;



		// add listeners to Deferred subordinates; treat others as resolved

		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 we're not waiting on anything, resolve the master

		if ( !remaining ) {

			deferred.resolveWith( resolveContexts, resolveValues );

		}



		return deferred.promise();

	}

  

 

你可能感兴趣的:(deferred)