zoukankan      html  css  js  c++  java
  • jquery源码之低调的回调函数队列--Callbacks

    jQuery中有一个很实用的函数队列,可能我们很少用到,但他在jQuery内部却有着举足轻重的地位。

    他就是Callbacks. jQuery作者用它构建了很多非常重要的模块。比如说$.Deferred。

    Callbacks 说白了就是个数组,里面存了很多函数对象。然而他真的 just so so么?

    好吧,爱因斯坦也只是个人,但他真的仅仅是个普普通通的人吗?Callbacks也不是。

    不说废话了,先看源码。

    // String to Object options format cache
    var optionsCache = {};
    
    // Convert String-formatted options into Object-formatted ones and store in cache
    function createOptions( options ) {
    	var object = optionsCache[ options ] = {};
    	jQuery.each( options.split( core_rspace ), function( _, flag ) {
    		object[ flag ] = true;
    	});
    	return object;
    }
    
    /*
     * Create a callback list using the following parameters:
     *
     *	options: an optional list of space-separated options that will change how
     *			the callback list behaves or a more traditional option object
     *
     * By default a callback list will act like an event callback list and can be
     * "fired" multiple times.
     *
     * Possible options:
     *
     *	once:			will ensure the callback list can only be fired once (like a Deferred)
     *
     *	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)
     *
     *	unique:			will ensure a callback can only be added once (no duplicate in the list)
     *
     *	stopOnFalse:	interrupt callings when a callback returns false
     *
     */
    jQuery.Callbacks = function( options ) {
    
    	// Convert options from String-formatted to Object-formatted if needed
    	// (we check in cache first)
    	options = typeof options === "string" ?
    		( optionsCache[ options ] || createOptions( options ) ) :
    		jQuery.extend( {}, options );
    
    	var // Last fire value (for non-forgettable lists)
    		memory,
    		// Flag to know if list was already fired
    		fired,
    		// 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,
    		// Actual callback list
    		list = [],
    		// Stack of fire calls for repeatable lists
    		stack = !options.once && [],
    		// Fire callbacks
    		fire = function( data ) {
    			memory = options.memory && data;
    			fired = true;
    			firingIndex = firingStart || 0;
    			firingStart = 0;
    			firingLength = list.length;
    			firing = true;
    			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
    				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
    					memory = false; // To prevent further calls using add
    					break;
    				}
    			}
    			firing = false;
    			if ( list ) {
    				if ( stack ) {
    					if ( stack.length ) {
    						fire( stack.shift() );
    					}
    				} else if ( memory ) {
    					list = [];
    				} else {
    					self.disable();
    				}
    			}
    		},
    		// Actual Callbacks object
    		self = {
    			// Add a callback or a collection of callbacks to the list
    			add: function() {
    				if ( list ) {
    					// First, we save the current length
    					var start = list.length;
    					(function add( args ) {
    						jQuery.each( args, function( _, arg ) {
    							var type = jQuery.type( arg );
    							if ( type === "function" ) {
    								if ( !options.unique || !self.has( arg ) ) {
    									list.push( arg );
    								}
    							} else if ( arg && arg.length && type !== "string" ) {
    								// Inspect recursively
    								add( arg );
    							}
    						});
    					})( 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
    					} else if ( memory ) {
    						firingStart = start;
    						fire( memory );
    					}
    				}
    				return this;
    			},
    			// Remove a callback from the list
    			remove: function() {
    				if ( list ) {
    					jQuery.each( arguments, function( _, arg ) {
    						var index;
    						while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
    							list.splice( index, 1 );
    							// Handle firing indexes
    							if ( firing ) {
    								if ( index <= firingLength ) {
    									firingLength--;
    								}
    								if ( index <= firingIndex ) {
    									firingIndex--;
    								}
    							}
    						}
    					});
    				}
    				return this;
    			},
    			// Control if a given callback is in the list
    			has: function( fn ) {
    				return jQuery.inArray( fn, list ) > -1;
    			},
    			// 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 ) {
    					self.disable();
    				}
    				return this;
    			},
    			// Is it locked?
    			locked: function() {
    				return !stack;
    			},
    			// Call all callbacks with the given context and arguments
    			fireWith: function( context, args ) {
    				args = args || [];
    				args = [ context, args.slice ? args.slice() : args ];
    				if ( list && ( !fired || stack ) ) {
    					if ( firing ) {
    						stack.push( args );
    					} else {
    						fire( 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 !!fired;
    			}
    		};
    
    	return self;
    };
    

     代码只有仅仅200行不到,但真正看起来却又点绕,

    《think in java》中有这么一句,理解一个程序最好的方法,就是把它看做一个服务的提供者。

    那他提供了那些服务:

    首先我们看看返回的self对象

    {
    	// 添加方法
    	add: function() {},
    	// 删除
    	remove: function() {},
    	// 是否包含
    	has: function() {},
    	// 清空
    	empty: function() {},
    	// 禁用
    	disable: function() {},
    	// 加锁
    	lock: function() {},
    	// 是否加锁
    	locked: function() {},
    	// 触发
    	fireWith: function(){},
    	fire: function() {},
    	// 是否触发
    	fired: function() {}
    }
    

      用途都十分清晰,那我们再看看参数,程序是服务的提供者,那么参数作为程序的入口的携带者,一般会用来装配一些属性。

    显然这里就是这样。

     先看Callbacks内部关于参数部分的代码。

            // 官方注释,将配置的options由string格式转换为object格式如果需要的话
            // Convert options from String-formatted to Object-formatted if needed
    	// (we check in cache first)
    	options = typeof options === "string" ?
    		// 注意这里, 这里去取optionsCache的值,或者调用
    		( optionsCache[ options ] || createOptions( options ) ) :
    		jQuery.extend( {}, options );
    

      在看看createOptions方法吧,其实就是个转换方法,还带有缓存功能。

    // String to Object options format cache
    // 建立一个缓存对象
    var optionsCache = {};
    
    // Convert String-formatted options into Object-formatted ones and store in cache
    function createOptions( options ) {
    	// 创建optionsCache中的options属性
    	var object = optionsCache[ options ] = {};
    	// 这里用到 each方法遍历
    	// options.split( core_rspace )  根据空格划分为数组
    	// _在jquery中通常用来作为占位符,即忽略的参数
    	jQuery.each( options.split( core_rspace ), function( _, flag ) {
    		// 遍历以后将切割后的每个属性设置为true
    		object[ flag ] = true;
    	});
    	return object;
    }
    // 可能例子会更清晰,
    var obj = createOptions( "once memory");
    /*
    obj;
    {
    	once: true,
    	memory: true
    }
    */    
    

      接下来就是具体的实现了,jQuery的实现一直是十分巧妙的,当然这可能仅仅是小菜我看来。

    /*
     * Create a callback list using the following parameters:
     *
     *	options: an optional list of space-separated options that will change how
     *			the callback list behaves or a more traditional option object
     *
     * By default a callback list will act like an event callback list and can be
     * "fired" multiple times.
     *
     * Possible options:
     *
     *	once:			will ensure the callback list can only be fired once (like a Deferred)
     *
     *	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)
     *
     *	unique:			will ensure a callback can only be added once (no duplicate in the list)
     *
     *	stopOnFalse:	interrupt callings when a callback returns false
     *
     */
     //
    jQuery.Callbacks = function( options ) {
    
    	// Convert options from String-formatted to Object-formatted if needed
    	// (we check in cache first)
    	options = typeof options === "string" ?
    		// 注意这里, 这里去取optionsCache的值,或者调用createOptions
    		// 我们看看createOptions函数
    		( optionsCache[ options ] || createOptions( options ) ) :
    		jQuery.extend( {}, options );
    
    	var // Last fire value (for non-forgettable lists)
    		// 以前触发的值(为了记忆的list,记忆了上次调用时所传递的基本信息(即记忆了参数))
    		memory,
    		// 是否触发
    		// Flag to know if list was already fired
    		fired,
    		// 是否正在触发
    		// Flag to know if list is currently firing
    		firing,
    		// 第一个被触发的function
    		// 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,
    		// 内部存放function的数组
    		// Actual callback list
    		list = [],
    		// 用来存放重复调用的数组,(当Callbacks被配置了 once属性,则为false)
    		// Stack of fire calls for repeatable lists
    		stack = !options.once && [],
    		// 内部触发函数,这里看到jquery隐藏信息的习惯了
    		// 作为该模块的核心方法
    		// 它没有暴露给外部,
    		// 《代码大全》 有提到信息隐藏的好处。
    		// Fire callbacks
    		fire = function( data ) {
    			// 在设置memory的情况下为 传递过来的参数data, 否则为undefined
    			memory = options.memory && data;
    			// 进入到这时标记已触发
    			fired = true;
    			// 当前触发索引设置为开始,或者0
    			firingIndex = firingStart || 0;
    			firingStart = 0;
    			firingLength = list.length;
    			firing = true;
    			// for循环触发list中的函数
    			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
    				// 如果stopOnFalse被设置,则检查调用函数后是否返回false
    				// 如果返回则终止触发,
    				// 注意触发参数 为一个多维数组
    				// data = [
    				//	context,
    				//	[args]
    				//]  这应该是由外部封装成固定格式,再传递过来的参数
    				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
    					memory = false; // To prevent further calls using add
    					break;
    				}
    			}
    			// 设置正在触发为false
    			firing = false;
    			// 如果list是存在的,即改callbacks还没有被禁用
    			if ( list ) {
    				// 如果 stack中有值,则递归调用
    				// 其实这里是判断是否设置了once属性
    				if ( stack ) {
    					if ( stack.length ) {
    						fire( stack.shift() );
    					}
    				} else if ( memory ) { // 如果设置记忆功能,则清空list(注意,是记忆需要调用的基本信息,即相关参数)
    					list = [];
    				} else {
    					// 只能调用一次,且不能使用memory,
    					// 则禁用
    					self.disable();
    				}
    			}
    		},
    		// 再来看看需要暴露的对象
    		// Actual Callbacks object
    		self = {
    			// 添加方法
    			// Add a callback or a collection of callbacks to the list
    			add: function() {
    				// list其实是可以作为是否禁用的标志的,
    				// 如果list存在
    				if ( list ) {
    					// First, we save the current length
    					var start = list.length;
    					// 真正的添加行为
    					// 用到了自执行
    					// 但又不是匿名函数,因为它可能需要递归
    					(function add( args ) {
    						jQuery.each( args, function( _, arg ) {
    							var type = jQuery.type( arg );
    							if ( type === "function" ) {
    								// 如果设置了唯一,且当前已包含该函数,
    								// 则不添加,反之则添加函数
    								if ( !options.unique || !self.has( arg ) ) {
    									list.push( arg );
    								}
    							} else if ( arg && arg.length && type !== "string" ) { // 递归调用
    								// Inspect recursively
    								add( arg );
    							}
    						});
    					})( arguments );
    					// Do we need to add the callbacks to the
    					// current firing batch?
    					// 如果正在触发,则只需要更新firingLength
    					if ( firing ) {
    						firingLength = list.length;
    					// With memory, if we're not firing then
    					// we should call right away
    					// 如果memory,则在添加的时候直接触发
    					} else if ( memory ) {
    						firingStart = start;
    						fire( memory );
    					}
    				}
    				return this;
    			},
    			// Remove a callback from the list
    			// 删除方法,遍历删除指定的方法,并维护好firingLength以及firingIndex
    			remove: function() {
    				if ( list ) {
    					jQuery.each( arguments, function( _, arg ) {
    						var index;
    						while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
    							list.splice( index, 1 );
    							// Handle firing indexes
    							if ( firing ) {
    								if ( index <= firingLength ) {
    									firingLength--;
    								}
    								if ( index <= firingIndex ) {
    									firingIndex--;
    								}
    							}
    						}
    					});
    				}
    				return this;
    			},
    			// Control if a given callback is in the list
    			// 是否包含
    			has: function( fn ) {
    				return jQuery.inArray( fn, list ) > -1;
    			},
    			// 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() {
    				// 看,这里有用到list是否存在来判断 是否被禁用
    				return !list;
    			},
    			// Lock the list in its current state
    			// 锁住即不能再被触发
    			// 如果没有设置memory则直接禁用
    			lock: function() {
    				stack = undefined;
    				if ( !memory ) {
    					self.disable();
    				}
    				return this;
    			},
    			// 是否加锁
    			// Is it locked?
    			locked: function() {
    				// 居然是判断stack是否存在
    				// 由此推断 加锁应该是设置智能触发一次
    				return !stack;
    			},
    			// Call all callbacks with the given context and arguments
    			fireWith: function( context, args ) {
    				args = args || [];
    				// 看这封装了arguments,用来内部fire函数的调用
    				args = [ context, args.slice ? args.slice() : args ];
    				// 如果还没被触发,或者允许触发多次
    				if ( list && ( !fired || stack ) ) {
    					// 正在触发,则添加到stack
    					// 在当次触发后,直接触发
    					if ( firing ) {
    						stack.push( args );
    					} else {
    						// 直接触发
    						fire( args );
    					}
    				}
    				return this;
    			},
    			// Call all the callbacks with the given arguments
    			// 设置context为this
    			fire: function() {
    				self.fireWith( this, arguments );
    				return this;
    			},
    			// To know if the callbacks have already been called at least once
    			fired: function() {
    				return !!fired;
    			}
    		};
    	// 注意有一个细节,self的所有方法都是返回的this
    	// 这表明,它是支持链式操作的
    	// jquery 很多地方用了这种优雅的技术
    	return self;
    };
    

      好吧,Callbacks就讲到这里了,神奇而低调的函数队列,在以后的源码中你也会经常看到他的身影,所以他能做什么并不用着急。

    但还是举些小例子用用看:

    var c = $.Callbacks("once memory");
    c.add(function(i) {
    	alert(123 + '-' + i);
    });
    c.add(function(i) {
    	alert(234 + '-' + i);
    });
    c.add(function(i) {
    	alert(456 + '-' + i);
    });
    c.fire('tianxia');
    // alert('123-tianxi'); alert('234-tianxi'); alert('456-tianxi'); 
    c.fire();
    // 再次调用,啥都没发生,因为设置了once
    // 什么都没发生
    c.add(function(i) {
    	alert(i);
    });
    // alert('tianxia')
    // 在设置memory,添加后,直接触发
    

      

  • 相关阅读:
    ie 中window.open无法带referrer
    es6 学习7 Set 和 Map 数据结构
    ES6 学习6 数组的扩展
    ES6 学习3 函数
    ES6学习之环境配置
    理解闭包
    javascript中实现继承的几种方式
    js 原型和原型链
    Vue学习之v-if与v-show的区别
    unity图集切成多张图片的方法
  • 原文地址:https://www.cnblogs.com/w2154/p/4570599.html
Copyright © 2011-2022 走看看