zoukankan      html  css  js  c++  java
  • jquery1.83 之前所有与异步列队相关的模块的演变回顾

    jQuery在1.5引入了Deferred对象(异步列队),当时它还没有划分为一个模块,放到核心模块中。直到1.52才分割出来。它拥有三个方法:_Deferred, Deferred与when。

    出于变量在不同作用域的共用,jQuery实现异步列队时不使用面向对象方式,它把_Deferred当作一个工厂方法,返回一个不透明的函数列队。之所以说不透明,是因为它的状态与元素都以闭包手段保护起来,只能通过列队对象提供的方法进行操作。这几个方法分别是done(添加函数),resolveWith(指定作用域地执行所有函数),resolve(执行所有函数),isResolved(判定是否已经调用过resolveWith或resolve方法),cancel(中断执行操作)。但_Deferred自始至终都作为一个内部方法,从没有在文档中公开过。

    Deferred在1.5是两个_Deferred的合体,但1+1不等于2,它还是做了增强。偷偷爆料,Deferred本来是python世界大名鼎鼎的Twisted框架的东西,由早期七大JS类库中的MochiKit取经回来,最后被dojo继承衣钵。jQuery之所以这样构造Deferred,分明不愿背抄袭的恶名,于是方法改得一塌糊涂,是jQuery命名最差的API,完全不知所云。它还加入当时正在热烈讨论的promise机制。下面是一个比较列表:

    dojo jQuery注解
    addBoth then同时添加正常回调与错误回调
    addCallback done添加正常回调
    addErrback fail 添加错误回调
    callback resolve执行所有正常回调
    errback reject执行所有错误回调
    resolveWith在指定作用域下执行所有正常回调,但dojo已经在addCallback上指定好了
    rejectWith在指定作用域下执行所有错误回调,但dojo已经在addErrback上指定好了
    promise返回一个外界不能改变其状态的Deferred对象(外称为Promise对象)
    //这里面没有改变异步列队状态的方法——resolve, resolveWith, reject, rejectWith	
    //Deferred存在三种状态,没触发,完成(没出错),出错
    promiseMethods = "then done fail isResolved isRejected promise".split( " " ),
    
    	// 创建一个不透明的函数列队
    	_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 ) {
    						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( jQuery.isFunction( this.promise ) ? this.promise() : 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: function( func ) {
    		var deferred = jQuery._Deferred(),
    			failDeferred = jQuery._Deferred(),
    			promise;
    		// Add errorDeferred methods, then and promise
    		jQuery.extend( deferred, {
    			then: function( doneCallbacks, failCallbacks ) {
    				deferred.done( doneCallbacks ).fail( failCallbacks );
    				return this;
    			},
    			fail: failDeferred.done,
    			rejectWith: failDeferred.resolveWith,
    			reject: failDeferred.resolve,
    			isRejected: failDeferred.isResolved,
                            //这是一个单例方法,一个异步列队只对应一个Promise对象,Promise可以说其代理人
    			promise: function( obj , i /* internal */ ) {
    				if ( obj == null ) {
    					if ( promise ) {//
    						return promise;
    					}
    					promise = obj = {};
    				}
    				i = promiseMethods.length;
    				while( i-- ) {
    					obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ];
    				}
    				return obj;
    			}
    		} );
    		// Make sure only one callback list will be used
    		deferred.then( failDeferred.cancel, deferred.cancel );
    		// Unexpose cancel
    		delete deferred.cancel;
    		// Call given func if any
    		if ( func ) {
    			func.call( deferred, deferred );
    		}
    		return deferred;
    	},
    
    	// 用于实现回调的回调
    	when: function( object ) {
    		var args = arguments,
    			length = args.length,
    			deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ?
    				object :
    				jQuery.Deferred(),
    			promise = deferred.promise(),
    			resolveArray;
    
    		if ( length > 1 ) {
    			resolveArray = new Array( length );
    			jQuery.each( args, function( index, element ) {
    				jQuery.when( element ).then( function( value ) {
    					resolveArray[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value;
    					if( ! --length ) {
    						deferred.resolveWith( promise, resolveArray );
    					}
    				}, deferred.reject );
    			} );
    		} else if ( deferred !== object ) {
    			deferred.resolve( object );
    		}
    		return promise;
    	},
    
    

    jQuery的when方法用于实现回调的回调,或者说,几个异列列队都执行后才执行另外的一些回调。这些后来的回调也是用done, when, fail添加的,但when返回的这个对象已经添加让用户控制它执行的能力了。因为这时它是种叫Promise的东西,只负责添加回调与让用户窥探其状态。一旦前一段回调都触发了,它就自然进入正常回调列队(deferred ,见Deferred方法的定义)或错误回调列队(failDeferred )中去。不过我这样讲,对于没有异步编程经验的人来说,肯定听得云里雾里。看实例好了。

      $.when({aa:1}, {aa:2}).done(function(a,b){
             console.log(a.aa)
              console.log(b.aa)
         });
    

    直接输出1,2。如果是传入两个函数,也是返回两个函数。因此对于普通的数据类型,前面的when有多少个参数,后面的done, fail方法的回调就有多少个参数。

                   function fn(){
                        return 4;
                    }
     
                    function log(s){
                      window.console && console.log(s)
                    }
                    $.when( { num:1 }, 2, '3', fn() ).done(function(o1, o2, o3, o4){
                        log(o1.num);
                        log(o2);
                        log(o3);
                        log(o4);
                    });
    

    如果我们想得到各个异步的结果,我们需要用resolve, resolveWith, reject, rejectWith进行传递它们。

                    var log = function(msg){
                        window.console && console.log(msg)
                    }
                    function asyncThing1(){
                        var dfd = $.Deferred();
                        setTimeout(function(){
                            log('asyncThing1 seems to be done...');
                            dfd.resolve('1111');
                        },1000);
                        return dfd.promise();
                    }
                    function asyncThing2(){
                        var dfd = $.Deferred();
                        setTimeout(function(){
                            log('asyncThing2 seems to be done...');
                            dfd.resolve('222');
                        },1500);
                        return dfd.promise();
                    }
                    function asyncThing3(){
                        var dfd = $.Deferred();
                        setTimeout(function(){
                            log('asyncThing3 seems to be done...');
                            dfd.resolve('333');
                        },2000);
                        return dfd.promise();
                    }
         
                    /* do it */
                    $.when( asyncThing1(), asyncThing2(), asyncThing3() ).done(function(res1, res2, res3){
                        log('all done!');
                        log(res1 + ', ' + res2 + ', ' + res3);
                    })
    

    异步列队一开始没什么人用(现在也没有什么人用,概念太抽象了,方法名起得太烂了),于是它只能在内部自产自销。首先被染指的是queue。queue模块是1.4为吸引社区的delay插件,特地从data模块中分化的产物,而data则是从event模块化分出来的。jQuery新模块的诞生总是因为用户对已有API的局限制不满而致。最早的queue模块的源码:

    jQuery.extend({
    	queue: function( elem, type, data ) {
    		if ( !elem ) {
    			return;
    		}
    
    		type = (type || "fx") + "queue";
    		var q = jQuery.data( elem, type );
    
    		// Speed up dequeue by getting out quickly if this is just a lookup
    		if ( !data ) {
    			return q || [];
    		}
    
    		if ( !q || jQuery.isArray(data) ) {
    			q = jQuery.data( elem, type, jQuery.makeArray(data) );
    
    		} else {
    			q.push( data );
    		}
    
    		return q;
    	},
    
    	dequeue: function( elem, type ) {
    		type = type || "fx";
    
    		var queue = jQuery.queue( elem, type ), fn = queue.shift();
    
    		// If the fx queue is dequeued, always remove the progress sentinel
    		if ( fn === "inprogress" ) {
    			fn = queue.shift();
    		}
    
    		if ( fn ) {
    			// Add a progress sentinel to prevent the fx queue from being
    			// automatically dequeued
    			if ( type === "fx" ) {
    				queue.unshift("inprogress");
    			}
    
    			fn.call(elem, function() {
    				jQuery.dequeue(elem, type);
    			});
    		}
    	}
    });
    
    jQuery.fn.extend({
    	queue: function( type, data ) {
    		if ( typeof type !== "string" ) {
    			data = type;
    			type = "fx";
    		}
    
    		if ( data === undefined ) {
    			return jQuery.queue( this[0], type );
    		}
    		return this.each(function( i, elem ) {
    			var queue = jQuery.queue( this, type, data );
    
    			if ( type === "fx" && queue[0] !== "inprogress" ) {
    				jQuery.dequeue( this, type );
    			}
    		});
    	},
    	dequeue: function( type ) {
    		return this.each(function() {
    			jQuery.dequeue( this, type );
    		});
    	},
    
    	// Based off of the plugin by Clint Helfers, with permission.
    	// http://blindsignals.com/index.php/2009/07/jquery-delay/
    	delay: function( time, type ) {
    		time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
    		type = type || "fx";
    
    		return this.queue( type, function() {
    			var elem = this;
    			setTimeout(function() {
    				jQuery.dequeue( elem, type );
    			}, time );
    		});
    	},
    
    	clearQueue: function( type ) {
    		return this.queue( type || "fx", [] );
    	}
    });
    
    

    1.6添加了_mark,_unmark,promise。queue是让函数同属一个队伍里面,目的是让动画一个接一个执行。_mark则是让它们各自拥有队伍,并列执行(虽然它们只记录异步列队中已被执行的函数个数)。promise则在这些并发执行的动画执行后才执行另些一些回调(或动画)。

    
    (function( jQuery ) {
    
    function handleQueueMarkDefer( elem, type, src ) {
        //清空记录deferred个数的字段,函数列队与异步列队
    	var deferDataKey = type + "defer",
    		queueDataKey = type + "queue",
    		markDataKey = type + "mark",
    		defer = jQuery.data( elem, deferDataKey, undefined, true );
    	if ( defer &&
    		( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) &&
    		( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) {
    		// Give room for hard-coded callbacks to fire first
    		// and eventually mark/queue something else on the element
    		setTimeout( function() {
    			if ( !jQuery.data( elem, queueDataKey, undefined, true ) &&
    				!jQuery.data( elem, markDataKey, undefined, true ) ) {
    				jQuery.removeData( elem, deferDataKey, true );
    				defer.resolve();
    			}
    		}, 0 );
    	}
    }
    
    jQuery.extend({
    
    	_mark: function( elem, type ) {
    		if ( elem ) {
    			type = (type || "fx") + "mark";//创建一个以mark为后缀的字段,用于记录此列队中个数
    			jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true );
    		}
    	},
    
    	_unmark: function( force, elem, type ) {
    		if ( force !== true ) {
    			type = elem;
    			elem = force;
    			force = false;
    		}
    		if ( elem ) {
    			type = type || "fx";
    			var key = type + "mark",
                            //让个数减1,如果第一个参数为true,就强逼减至0
    				count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 );
    			if ( count ) {
    				jQuery.data( elem, key, count, true );
    			} else {//如果为0,就移除它
    				jQuery.removeData( elem, key, true );
    				handleQueueMarkDefer( elem, type, "mark" );
    			}
    		}
    	},
    
    	queue: function( elem, type, data ) {
    		if ( elem ) {
    			type = (type || "fx") + "queue";
    			var q = jQuery.data( elem, type, undefined, true );
    			// Speed up dequeue by getting out quickly if this is just a lookup
    			if ( data ) {
    				if ( !q || jQuery.isArray(data) ) {
    					q = jQuery.data( elem, type, jQuery.makeArray(data), true );
    				} else {
    					q.push( data );
    				}
    			}
    			return q || [];
    		}
    	},
    
    	dequeue: function( elem, type ) {
    		type = type || "fx";
    
    		var queue = jQuery.queue( elem, type ),
    			fn = queue.shift(),
    			defer;
    
    		// If the fx queue is dequeued, always remove the progress sentinel
    		if ( fn === "inprogress" ) {
    			fn = queue.shift();
    		}
    
    		if ( fn ) {
    			// Add a progress sentinel to prevent the fx queue from being
    			// automatically dequeued
    			if ( type === "fx" ) {
    				queue.unshift("inprogress");
    			}
    
    			fn.call(elem, function() {
    				jQuery.dequeue(elem, type);
    			});
    		}
    
    		if ( !queue.length ) {
    			jQuery.removeData( elem, type + "queue", true );
    			handleQueueMarkDefer( elem, type, "queue" );
    		}
    	}
    });
    
    jQuery.fn.extend({
    	queue: function( type, data ) {
    		if ( typeof type !== "string" ) {
    			data = type;
    			type = "fx";
    		}
    
    		if ( data === undefined ) {
    			return jQuery.queue( this[0], type );
    		}
    		return this.each(function() {
    			var queue = jQuery.queue( this, type, data );
    
    			if ( type === "fx" && queue[0] !== "inprogress" ) {
    				jQuery.dequeue( this, type );
    			}
    		});
    	},
    	dequeue: function( type ) {
    		return this.each(function() {
    			jQuery.dequeue( this, type );
    		});
    	},
    	// Based off of the plugin by Clint Helfers, with permission.
    	// http://blindsignals.com/index.php/2009/07/jquery-delay/
    	delay: function( time, type ) {
    		time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
    		type = type || "fx";
    
    		return this.queue( type, function() {
    			var elem = this;
    			setTimeout(function() {
    				jQuery.dequeue( elem, type );
    			}, time );
    		});
    	},
    	clearQueue: function( type ) {
    		return this.queue( type || "fx", [] );
    	},
    
            //把jQuery对象装进一个异步列队,允许它在一系列动画中再执行之后绑定的回调
    	promise: function( type, object ) {
    		if ( typeof type !== "string" ) {
    			object = type;
    			type = undefined;
    		}
    		type = type || "fx";
    		var defer = jQuery.Deferred(),
    			elements = this,
    			i = elements.length,
    			count = 1,
    			deferDataKey = type + "defer",
    			queueDataKey = type + "queue",
    			markDataKey = type + "mark";
    		function resolve() {
    			if ( !( --count ) ) {
    				defer.resolveWith( elements, [ elements ] );
    			}
    		}
    		while( i-- ) {
                        //如果它之前已经使用过unmark, queue等方法,那么我们将生成一个新的Deferred放进缓存系统
    			if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
    					( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
    						jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
    					jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) {
    				count++;
    				tmp.done( resolve );
    			}
    		}
    		resolve();
    		return defer.promise();
    	}
    });
    
    })( jQuery );
    

    jQuery.ajax模块也被染指,$.XHR对象,当作XMLHttpRequest 对象的仿造器是由一个Deferred对象与一个_Deferred的对象构成。

    
    deferred = jQuery.Deferred(),
    completeDeferred = jQuery._Deferred(),
    jqXHR  ={/**/}
    //....
    deferred.promise( jqXHR );
    jqXHR.success = jqXHR.done;
    jqXHR.error = jqXHR.fail;
    jqXHR.complete = completeDeferred.done;
    
    

    jQuery1.7,从deferred模块中分化出callback模块,其实就是之前的_Deferred的增强版,添加去重,锁定,return false时中断执行下一个回调,清空等功能。

    (function( jQuery ) {
    
    // String to Object flags format cache
    var flagsCache = {};
    
    // Convert String-formatted flags into Object-formatted ones and store in cache
    function createFlags( flags ) {
    	var object = flagsCache[ flags ] = {},
    		i, length;
    	flags = flags.split( /\s+/ );
    	for ( i = 0, length = flags.length; i < length; i++ ) {
    		object[ flags[i] ] = true;
    	}
    	return object;
    }
    
    /*
     * 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:			只执行一次
     *
     *	memory:			模仿domReady的行为
     *
     *	unique:			去重
     *
     *	stopOnFalse:	        模仿事件回调中的return false中断传播的行为
     *
     */
    jQuery.Callbacks = function( flags ) {
    
    	// Convert flags from String-formatted to Object-formatted
    	// (we check in cache first)
    	flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
    
    	var // Actual callback list
    		list = [],
    		// Stack of fire calls for repeatable lists
    		stack = [],
    		// Last fire value (for non-forgettable lists)
    		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
    					if ( !flags.unique || !self.has( elem ) ) {
    						list.push( elem );
    					}
    				}
    			}
    		},
    		// Fire callbacks
    		fire = function( context, args ) {
    			args = args || [];
    			memory = !flags.memory || [ context, args ];
    			firing = true;
    			firingIndex = firingStart || 0;
    			firingStart = 0;
    			firingLength = list.length;
    			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
    				if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
    					memory = true; // Mark as halted
    					break;
    				}
    			}
    			firing = false;
    			if ( list ) {
    				if ( !flags.once ) {
    					if ( stack && stack.length ) {
    						memory = stack.shift();
    						self.fireWith( memory[ 0 ], memory[ 1 ] );
    					}
    				} else if ( memory === true ) {
    					self.disable();
    				} else {
    					list = [];
    				}
    			}
    		},
    		// Actual Callbacks object
    		self = {
    			// Add a callback or a collection of callbacks to the list
    			add: function() {
    				if ( list ) {
    					var length = list.length;
    					add( arguments );
    					// Do we need to add the callbacks to the
    					// current firing batch?
    					if ( firing ) {
    						firingLength = list.length;
    					// With memory, if we're not firing then
    					// we should call right away, unless previous
    					// firing was halted (stopOnFalse)
    					} 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++ ) {
    							if ( args[ argIndex ] === list[ i ] ) {
    								// Handle firingIndex and firingLength
    								if ( firing ) {
    									if ( i <= firingLength ) {
    										firingLength--;
    										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: function() {
    				list = stack = memory = undefined;
    				return this;
    			},
    			// Is it disabled?
    			disabled: function() {
    				return !list;
    			},
    			// Lock the list in its current state
    			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 ) {
    							stack.push( [ context, args ] );
    						}
    					} else if ( !( flags.once && memory ) ) {
    						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
    			fired: function() {
    				return !!memory;
    			}
    		};
    
    	return self;
    };
    
    })( jQuery );
    

    这期间有还个小插曲,jQuery团队还想增加一个叫Topic的模块,内置发布者订阅者机制,但这封装太溥了,结果被否决。

    (function( jQuery ) {
    
    	var topics = {},
    		sliceTopic = [].slice;
    
    	jQuery.Topic = function( id ) {
    		var callbacks,
    			method,
    			topic = id && topics[ id ];
    		if ( !topic ) {
    			callbacks = jQuery.Callbacks();
    			topic = {
    				publish: callbacks.fire,
    				subscribe: callbacks.add,
    				unsubscribe: callbacks.remove
    			};
    			if ( id ) {
    				topics[ id ] = topic;
    			}
    		}
    		return topic;
    	};
    
    	jQuery.extend({
    		subscribe: function( id ) {
    			var topic = jQuery.Topic( id ),
    				args = sliceTopic.call( arguments, 1 );
    			topic.subscribe.apply( topic, args );
    			return {
    				topic: topic,
    				args: args
    			};
    		},
    		unsubscribe: function( id ) {
    			var topic = id && id.topic || jQuery.Topic( id );
    			topic.unsubscribe.apply( topic, id && id.args ||
    					sliceTopic.call( arguments, 1 ) );
    		},
    		publish: function( id ) {
    			var topic = jQuery.Topic( id );
    			topic.publish.apply( topic, sliceTopic.call( arguments, 1 ) );
    		}
    	});
    
    })( jQuery );
    

    虽然把大量代码移动callbacks,但1.7的Deferred却一点没有没变小,它变得更重型,它由三个函数列队组成了。并且返回的是Promise对象,比原来多出了pipe, state, progress, always方法。ajax那边就变成这样:

    
    deferred = jQuery.Deferred(),
    completeDeferred = jQuery.Callbacks( "once memory" ),
    
    deferred.promise( jqXHR );
    jqXHR.success = jqXHR.done;
    jqXHR.error = jqXHR.fail;
    jqXHR.complete = completeDeferred.add;
    

    queue那边也没变多少。

    //1.72
    (function( jQuery ) {
    
    function handleQueueMarkDefer( elem, type, src ) {
    	var deferDataKey = type + "defer",
    		queueDataKey = type + "queue",
    		markDataKey = type + "mark",
    		defer = jQuery._data( elem, deferDataKey );
    	if ( defer &&
    		( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
    		( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
    		// Give room for hard-coded callbacks to fire first
    		// and eventually mark/queue something else on the element
    		setTimeout( function() {
    			if ( !jQuery._data( elem, queueDataKey ) &&
    				!jQuery._data( elem, markDataKey ) ) {
    				jQuery.removeData( elem, deferDataKey, true );
    				defer.fire();
    			}
    		}, 0 );
    	}
    }
    
    jQuery.extend({
    
    	_mark: function( elem, type ) {
    		if ( elem ) {
    			type = ( type || "fx" ) + "mark";
    			jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
    		}
    	},
    
    	_unmark: function( force, elem, type ) {
    		if ( force !== true ) {
    			type = elem;
    			elem = force;
    			force = false;
    		}
    		if ( elem ) {
    			type = type || "fx";
    			var key = type + "mark",
    				count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
    			if ( count ) {
    				jQuery._data( elem, key, count );
    			} else {
    				jQuery.removeData( elem, key, true );
    				handleQueueMarkDefer( elem, type, "mark" );
    			}
    		}
    	},
    
    	queue: function( elem, type, data ) {
    		var q;
    		if ( elem ) {
    			type = ( type || "fx" ) + "queue";
    			q = jQuery._data( elem, type );
    
    			// Speed up dequeue by getting out quickly if this is just a lookup
    			if ( data ) {
    				if ( !q || jQuery.isArray(data) ) {
    					q = jQuery._data( elem, type, jQuery.makeArray(data) );
    				} else {
    					q.push( data );
    				}
    			}
    			return q || [];
    		}
    	},
    
    	dequeue: function( elem, type ) {
    		type = type || "fx";
    
    		var queue = jQuery.queue( elem, type ),
    			fn = queue.shift(),
    			hooks = {};
    
    		// If the fx queue is dequeued, always remove the progress sentinel
    		if ( fn === "inprogress" ) {
    			fn = queue.shift();
    		}
    
    		if ( fn ) {
    			// Add a progress sentinel to prevent the fx queue from being
    			// automatically dequeued
    			if ( type === "fx" ) {
    				queue.unshift( "inprogress" );
    			}
    
    			jQuery._data( elem, type + ".run", hooks );
    			fn.call( elem, function() {
    				jQuery.dequeue( elem, type );
    			}, hooks );
    		}
    
    		if ( !queue.length ) {
    			jQuery.removeData( elem, type + "queue " + type + ".run", true );
    			handleQueueMarkDefer( elem, type, "queue" );
    		}
    	}
    });
    
    jQuery.fn.extend({
    	queue: function( type, data ) {
    		var setter = 2;
    
    		if ( typeof type !== "string" ) {
    			data = type;
    			type = "fx";
    			setter--;
    		}
    
    		if ( arguments.length < setter ) {
    			return jQuery.queue( this[0], type );
    		}
    
    		return data === undefined ?
    			this :
    			this.each(function() {
    				var queue = jQuery.queue( this, type, data );
    
    				if ( type === "fx" && queue[0] !== "inprogress" ) {
    					jQuery.dequeue( this, type );
    				}
    			});
    	},
    	dequeue: function( type ) {
    		return this.each(function() {
    			jQuery.dequeue( this, type );
    		});
    	},
    	// Based off of the plugin by Clint Helfers, with permission.
    	// http://blindsignals.com/index.php/2009/07/jquery-delay/
    	delay: function( time, type ) {
    		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
    		type = type || "fx";
    
    		return this.queue( type, function( next, hooks ) {
    			var timeout = setTimeout( next, time );
    			hooks.stop = function() {
    				clearTimeout( timeout );
    			};
    		});
    	},
    	clearQueue: function( type ) {
    		return this.queue( type || "fx", [] );
    	},
    	// Get a promise resolved when queues of a certain type
    	// are emptied (fx is the type by default)
    	promise: function( type, object ) {
    		if ( typeof type !== "string" ) {
    			object = type;
    			type = undefined;
    		}
    		type = type || "fx";
    		var defer = jQuery.Deferred(),
    			elements = this,
    			i = elements.length,
    			count = 1,
    			deferDataKey = type + "defer",
    			queueDataKey = type + "queue",
    			markDataKey = type + "mark",
    			tmp;
    		function resolve() {
    			if ( !( --count ) ) {
    				defer.resolveWith( elements, [ elements ] );
    			}
    		}
    		while( i-- ) {
    			if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
    					( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
    						jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
    					jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
    				count++;
    				tmp.add( resolve );
    			}
    		}
    		resolve();
    		return defer.promise( object );
    	}
    });
    
    })( jQuery );
    

    这时候,钩子机制其实已经在jQuery内部蔓延起来,1.5是css模块的cssHooks,1.6是属性模块的attrHooks, propHooks, boolHooks, nodeHooks,1.7是事件模块的fixHooks, keyHooks, mouseHooks,1.8是queue模块的_queueHooks,由于_queueHooks,queue终于瘦身了。

    //1.8
    jQuery.extend({
    	queue: function( elem, type, data ) {
    		var queue;
    
    		if ( elem ) {
    			type = ( type || "fx" ) + "queue";
    			queue = jQuery._data( elem, type );
    
    			// Speed up dequeue by getting out quickly if this is just a lookup
    			if ( data ) {
    				if ( !queue || jQuery.isArray(data) ) {
    					queue = jQuery._data( elem, type, jQuery.makeArray(data) );
    				} else {
    					queue.push( data );
    				}
    			}
    			return queue || [];
    		}
    	},
    
    	dequeue: function( elem, type ) {
    		type = type || "fx";
    
    		var queue = jQuery.queue( elem, type ),
    			fn = queue.shift(),
    			hooks = jQuery._queueHooks( elem, type ),
    			next = function() {
    				jQuery.dequeue( elem, type );
    			};
    
    		// If the fx queue is dequeued, always remove the progress sentinel
    		if ( fn === "inprogress" ) {
    			fn = queue.shift();
    		}
    
    		if ( fn ) {
    
    			// Add a progress sentinel to prevent the fx queue from being
    			// automatically dequeued
    			if ( type === "fx" ) {
    				queue.unshift( "inprogress" );
    			}
    
    			// clear up the last queue stop function
    			delete hooks.stop;
    			fn.call( elem, next, hooks );
    		}
    		if ( !queue.length && hooks ) {
    			hooks.empty.fire();
    		}
    	},
    
    	// not intended for public consumption - generates a queueHooks object, or returns the current one
    	_queueHooks: function( elem, type ) {
    		var key = type + "queueHooks";
    		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
    			empty: jQuery.Callbacks("once memory").add(function() {
    				jQuery.removeData( elem, type + "queue", true );
    				jQuery.removeData( elem, key, true );
    			})
    		});
    	}
    });
    
    jQuery.fn.extend({
    	queue: function( type, data ) {
    		var setter = 2;
    
    		if ( typeof type !== "string" ) {
    			data = type;
    			type = "fx";
    			setter--;
    		}
    
    		if ( arguments.length < setter ) {
    			return jQuery.queue( this[0], type );
    		}
    
    		return data === undefined ?
    			this :
    			this.each(function() {
    				var queue = jQuery.queue( this, type, data );
    
    				// ensure a hooks for this queue
    				jQuery._queueHooks( this, type );
    
    				if ( type === "fx" && queue[0] !== "inprogress" ) {
    					jQuery.dequeue( this, type );
    				}
    			});
    	},
    	dequeue: function( type ) {
    		return this.each(function() {
    			jQuery.dequeue( this, type );
    		});
    	},
    	// Based off of the plugin by Clint Helfers, with permission.
    	// http://blindsignals.com/index.php/2009/07/jquery-delay/
    	delay: function( time, type ) {
    		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
    		type = type || "fx";
    
    		return this.queue( type, function( next, hooks ) {
    			var timeout = setTimeout( next, time );
    			hooks.stop = function() {
    				clearTimeout( timeout );
    			};
    		});
    	},
    	clearQueue: function( type ) {
    		return this.queue( type || "fx", [] );
    	},
    	// Get a promise resolved when queues of a certain type
    	// are emptied (fx is the type by default)
    	promise: function( type, obj ) {
    		var tmp,
    			count = 1,
    			defer = jQuery.Deferred(),
    			elements = this,
    			i = this.length,
    			resolve = function() {
    				if ( !( --count ) ) {
    					defer.resolveWith( elements, [ elements ] );
    				}
    			};
    
    		if ( typeof type !== "string" ) {
    			obj = type;
    			type = undefined;
    		}
    		type = type || "fx";
    
    		while( i-- ) {
    			if ( (tmp = jQuery._data( elements[ i ], type + "queueHooks" )) && tmp.empty ) {
    				count++;
    				tmp.empty.add( resolve );
    			}
    		}
    		resolve();
    		return defer.promise( obj );
    	}
    });
    

    同时,动画模块迎来了它第三次大重构,它也有一个钩子Tween.propHooks。它多出两个对象,其中Animation返回一个异步列队,Tween 是用于处理单个样式或属性的变化,相当于之前Fx对象。animate被抽空了,它在1.72可是近百行的规模。jQuery通过钩子机制与分化出一些新的对象,将一些巨型方法重构掉。现在非常长的方法只龟缩在节点模块,回调模块。

    
    	animate: function( prop, speed, easing, callback ) {
    		var empty = jQuery.isEmptyObject( prop ),
    			optall = jQuery.speed( speed, easing, callback ),
    			doAnimation = function() {
    				// Operate on a copy of prop so per-property easing won't be lost
    				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
    
    				// Empty animations resolve immediately
    				if ( empty ) {
    					anim.stop( true );
    				}
    			};
    
    		return empty || optall.queue === false ?
    			this.each( doAnimation ) :
    			this.queue( optall.queue, doAnimation );
    	},
    

    到目前为止,所有异步的东西都被jQuery改造成异步列队的“子类”或叫“变种”更合适些。如domReady, 动画,AJAX,与执行了promise或delay或各种特效方法之后的jQuery对象。于是所有异步的东西在promise的加护下,像同步那样编写异步程序。

    http://www.bitstorm.org/weblog/2012-1/Deferred_and_promise_in_jQuery.html http://hamalog.tumblr.com/post/5159447047/jquery-deferred
    机器瞎学/数据掩埋/模式混淆/人工智障/深度遗忘/神经掉线/计算机幻觉/专注单身二十五年
  • 相关阅读:
    小程序购物车页面样式
    用css让div高度自动撑满屏幕
    客户端封装浏览器
    vuex模块的普通用法
    vue图片预览
    Django中Cookie和Session配置和操作
    jira的插件开发流程实践
    python3.7安装模块MySQLdb报错error: Microsoft Visual C++ 14.0 is required.
    Hadoop2.9下运行JAR包时System.out.println的输出日志
    MapReduce程序——WordCount(Windows_Eclipse + Ubuntu14.04_Hadoop2.9.0)
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2765576.html
Copyright © 2011-2022 走看看