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

  

你可能感兴趣的:(callback)