jqueryソースコードの控えめなコールバック関数キュー--Callbacks

15112 ワード

jQueryには実用的な関数キューがあり、私たちはあまり使わないかもしれませんが、彼はjQuery内部で重要な地位を持っています.
彼はCallbacksだjQueryの著者らはそれを用いて多くの非常に重要なモジュールを構築した.例えば$Deferred.
Callbacksははっきりと配列で、関数オブジェクトがたくさん保存されています.しかし彼は本当にjust 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,   ,