JQuery源碼淺析: Callbacks

1.定義

  回調這個詞對每個js使用者是如此的熟悉不過. 從字面看很容易理解,Callback就是對調,Callbacks就是多個回掉.在JQuery中就是回調隊列, 也就是Callbacks中保存着很多個回調函數隊列, 也就是按照順序執行(按照加入隊列的順序觸發這些函數,並不意味着第一個運行結束才調用第二個回調,因爲js中存在異步)隊列中的每一個回調函數. 我們也可以這麼理解,也就是我們常說的事件訂閱和發佈模式. 說道事件,我們就容易理解了.每一次trigger就對調用一個回調函數.

  那麼Callbacks有什麼用呢?在JQuery中,Deffered中使用Callbacks實現了Promise/A+標準. 解決異步的同步問題,解決代碼深層嵌套.


2.Callbacks中的主要方法

callbacks.add()        回調列表中添加一個回調或回調的集合。
callbacks.disable()    禁用回調列表中的回調
callbacks.disabled()   確定回調列表是否已被禁用。 
callbacks.empty()      從列表中刪除所有的回調.
callbacks.fire()       用給定的參數調用所有的回調
callbacks.fired()      訪問給定的上下文和參數列表中的所有回調。 
callbacks.fireWith()   訪問給定的上下文和參數列表中的所有回調。
callbacks.has()        確定列表中是否提供一個回調
callbacks.lock()       鎖定當前狀態的回調列表。
callbacks.locked()     確定回調列表是否已被鎖定。
callbacks.remove()     從回調列表中的刪除一個回調或回調集合。
3.主要參數

    options = {  
        once:       回調對象僅觸發(fire)一次  
     
        memory:     若true,在Callbacks正則觸發事件過程中,新加入回調函數,新加入的函數將在舊的回調函數隊列執行完成之後,促發新加入的.
     
        unique:     在add操作中,相同的函數僅只一次被添加(push)到回調列表中  
     
        stopOnFalse:當回調函數返回false,中斷列表中的回調循環調用,且memory === false,阻止在add操作中將要觸發的回調  
    } 
4.源碼
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 // Flag to know if list is currently firing
		firing,//是否正在觸發
		// Last fire value (for non-forgettable lists)
		memory,//
		// Flag to know if list was already fired
		fired,//是否已經觸發過
		// End of the loop when firing
		firingLength,//回調隊列的長度
		// Index of currently firing callback (modified by remove if needed)
		firingIndex,//標記當前觸發隊列的標號(也就是記錄當前觸發到第幾個)
		// First callback to fire (used internally by add and fireWith)
		firingStart,//回調隊列的起始標號
		// Actual callback list
		list = [],//回調隊列, 保存回調函數
		// Stack of fire calls for repeatable lists
		//觸發隊列, 多次觸發的隊列才把觸發的上下文入棧
		//(同一個時間只有一次觸發隊列執行,也就是說連續觸發fire兩次隊列,第一次執行完,纔會執行第二次[第一次觸發,第二次觸發...])
		stack = !options.once && [],
		// Fire callbacks
		//觸發回調函數, self使用函數fire實現最終的回調函數的調用
		fire = function( data ) {
			//如果是memory類型管理器, 記住fire的事件data,以便下次add的時候可以重新fire這個事件
			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
		//var cb = $.Callbacks();返回的實際是self對象, 在JQuery中很多這樣的使用.使用閉包實現開閉和封裝.
		//有點類似Java中的內部類,例如使用內部類Iterator 實現對容器的遍歷.通過類提供的接口,限制訪問實際外部類的東西.
		//self作爲Callbacks的對外接口,我們使用它來訪問Callbacks
		self = {
			// Add a callback or a collection of callbacks to the list
			add: function() {
				if ( list ) {
					// First, we save the current length
					//記錄本次觸發的隊列長度位置,當是memory的時候可以接着觸發新加入的回調函數
					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;
			},
			// Check if a given callback is in the list.
			// If no argument is given, return whether or not list has callbacks attached.
			has: function( fn ) {
				return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
			},
			// Remove all callbacks from the list
			empty: function() {
				list = [];
				firingLength = 0;
				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 ) {
				if ( list && ( !fired || stack ) ) {
					args = args || [];
					//棧中存的上下文格式是[上下文(默認self爲上下文), [參數1, 參數2...]]
					args = [ context, args.slice ? args.slice() : args ];
					//觸發回調隊列的時候,如果上一次觸發還正在進行,那麼久把當前觸發的上下文和參數保存到棧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;
			}
		};
	//返回self
	return self;
};



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章