zoukankan      html  css  js  c++  java
  • 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();
    	}
    

      

  • 相关阅读:
    Are You Safer With Firefox?(zz)
    IIS+PHP下调用WebService初试
    垃圾链接和网络欺骗
    微软即将发布64位XP和Win2003 SP1(zz)
    今日个人大事记:)
    GT4 Web Service编译和发布初探
    纪念一下QQ等级和在线时长
    今天安装GT3.9.5碰到的问题
    判断32位整数二进制中1的个数
    Windows 2003 SP1新体验
  • 原文地址:https://www.cnblogs.com/w2154/p/4573447.html
Copyright © 2011-2022 走看看