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,添加后,直接触发
    

      

  • 相关阅读:
    [SSRS] Use Enum values in filter expressions Dynamics 365 Finance and Operation
    Power shell deploy all SSRS report d365 FO
    display method in Dynamics 365 FO
    How To Debug Dynamics 365 Finance and Operation
    Computed columns and virtual fields in data entities Dynamics 365
    Azure DevOps for Power Platform Build Pipeline
    Create readonly entities that expose financial dimensions Dynamics 365
    Dataentity call stack dynamics 365
    Dynamics 365 FO extension
    Use singletenant servertoserver authentication PowerApps
  • 原文地址:https://www.cnblogs.com/w2154/p/4570599.html
Copyright © 2011-2022 走看看