在回調函數列表內部,通過一個數組來保存回調函數,其他方法則圍繞這個數組進行操作和檢測。
回調函數列表支持添加、移除、觸發、鎖定和禁用回調函數,為jQuery.ajax()、jQuery.Deferred()和ready事件提供基礎功能,我們也可以基於它編寫新得組件。
// 代碼行 3190——3201 var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); // Convert String-formatted options into Object-formatted ones // 工具函數,將字符串格式得標記轉換為對象格式得標記。 function createOptions( options ) { var object = {}; // 對象object用於存儲轉換后得標記對象,其屬性為標記字符串,屬性值為true。 jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { // 用空白符吧標記字符串分割為數組,然后遍歷數組,為返回值object,添加單個標記,屬性值一律為true。 object[ flag ] = true; } ); // 最后返回對象格式得標記 return object; } // 代碼行:3203——3417 /* * 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 ) { // 參數options是可選得字符串,用以改變回調函數列表得行為。如果不傳入參數options,則回調函數列表得行為類似於事件監聽函數,能夠被觸發多次;如果傳入參數options,則參數值可以是單個標記或多個標記得組合,多個標記之間用空格隔開。 // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) // 解析字符串標記options為對象 // 如果options是字符串,調用工具函數createOptions(options)將標記字符串options解析為標記對象,否則調用jquery.extend()將options中的內容合並到一個空對象中 options = typeof options === "string" ? createOptions( options ) : jQuery.extend( {}, options ); // 聲明局部變量,通過閉包機制引用 var // Flag to know if list is currently firing // 標記,以知道回調函數列表是否正在執行中 firing, // Last fire value for non-forgettable lists // memory得可能值和用途有些復雜,在分析工具fire(context,args)時詳細分析 memory, // Flag to know if list was already fired // 標記,以知道list是否已被觸發 fired, // Flag to prevent firing // 標記,防止firing locked, // Actual callback list // 存放回調函數得數組 list = [], // Queue of execution data for repeatable lists // 在可重復觸發、正在執行得列表上,重復觸發時,將上下文和參數放入數組queue中 queue = [], // Index of currently firing callback (modified by add/remove as needed) // 當前正在執行得回調函數得下標 firingIndex = -1, // Fire callbacks // 實際觸發回調函數得工具函數 // 工具函數fire()調用數組list中得回調函數。該函數通過閉包機制引用數組list fire = function() { // Enforce single-firing locked = locked || options.once; // Execute callbacks for all pending executions, // respecting firingIndex overrides and runtime changes fired = firing = true; for ( ; queue.length; firingIndex = -1 ) { // 變量memory的初始值為undefined,表示當前回調函數列表未被觸發過。 // 在memory模式下,變量memory的值為[context,args],間接地表示回調函數列表已經被觸發過。 // 在非memory模式下,變量memory的值為true,間接地表示回調函數列表已經被觸發過;如果同時是once模式,則不再執行任何回調函數。 // 在stopOnFalse模式下,如果某個回調函數返回了false,則變量memory的值為true,間接地表示回調函數列表已經被觸發過。 memory = queue.shift(); while ( ++firingIndex < list.length ) { // Run callback and check for early termination // 執行回調函數list[firingIndex],如果返回值是false,並且當前回調函數列表是stopOnFalse模式,則變量memory則賦值為false,並且停止執行后續得其他回調函數,也間接表示了當前回調函數列表已經被觸發過。 if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && options.stopOnFalse ) { // Jump to end and forget the data so .add doesn't re-fire firingIndex = list.length; memory = false; } } } // Forget the data if we're done with it if ( !options.memory ) { memory = false; } firing = false; // Clean up if we're done firing for good if ( locked ) { // Keep an empty list if we have data for future add calls // 如果我們有用於將來添加調用的數據,請保留一個空列表 if ( memory ) { list = []; // Otherwise, this object is spent // 否則,此對象將被使用 } else { list = ""; } } }, // Actual Callbacks object // 回調函數列表,方法jQuery.callbacks(flags)得返回值。 self = { // Add a callback or a collection of callbacks to the list // 添加一個或一組回調函數到回調函數列表中 add: function() { if ( list ) { // If we have memory from a past run, we should fire after adding // 如果我們有過去運行時的內存,則應該在添加之后觸發 if ( memory && !firing ) { // 在開始添加回調函數前,先備份數組list的長度,該值也是回調函數的插入位置 firingIndex = list.length - 1; queue.push( memory ); } // 調用工具函數add()添加回調函數 ( function add( args ) { jQuery.each( args, function( _, arg ) { if ( isFunction( arg ) ) { if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } } else if ( arg && arg.length && toType( arg ) !== "string" ) { // Inspect recursively add( arg ); } } ); } )( arguments ); // 在memory模式下,如果回調函數列表未在執行中,並且已經被觸發過,則修正起始下標為回調函數的插入位置,然后調用工具函數fire()立即執行添加新的回調函數。 if ( memory && !firing ) { fire(); } } // 返回當前回調函數列表,以保持鏈式語法 return this; }, // Remove a callback from the list // 從回調函數列表中移除一個或一組回調函數 // 方法callbacks.remove()用於從回調函數列表中移除一個或一組回調函數,移除前修正當前下標firingIndex,確保移除的同時不會漏執行回調函數。 remove: function() { // 遍歷待移除的回調函數數組,在循環體內嵌套遍歷已有的回調函數數組,如果檢查到某個待移除回調函數與某個已有的回調函數完全相等,則從已有的回調函數數組中移除它。 jQuery.each( arguments, function( _, arg ) { var index; while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { // 調用數組原型方法splice(),從匹配下標處開始移除1個元素, list.splice( index, 1 ); // Handle firing indexes // 如果待移除函數的下標小於正在執行回調函數的下標firingIndex,即待移除的回調函數已經執行,則修正firingIndex減1,以確保不會漏執行回調函數。 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.length > 0; }, // Remove all callbacks from the list // 清空列表,移除回調函數列表中得所有回調函數 empty: function() { if ( list ) { list = []; } return this; }, // Disable .fire and .add // Abort any current/pending executions // Clear all callbacks and values // 禁用回調函數列表,不在做任何事情 disable: function() { // 設置變量locked、queue為空數組,list,memory為“” // 在方法callbacks.add()、callback.remove()中,如果變量list可以轉換為false,則忽略本次調用,即無法添加、移除回調函數;在方法callback.fireWith()中,如果變量locked的狀態可以轉換為false,則忽略本次調用,如果可以轉換為true,才有可能觸發回調函數。 locked = queue = []; list = memory = ""; return this; }, // 判斷回調函數列表是否已被禁用 disabled: function() { return !list; }, // Disable .fire // Also disable .add unless we have memory (since it would have no effect) // Abort any pending executions // 鎖定回調函數列表 lock: function() { locked = queue = []; if ( !memory && !firing ) { list = memory = ""; } return this; }, // 判斷回調函數列表是否已被鎖定 locked: function() { return !!locked; }, // Call all callbacks with the given context and arguments // 使用指定得上下文和參數觸發回調函數列表中得所有回調函數 fireWith: function( context, args ) { // 先檢測變量locked的狀態,如果可以轉換為false,則忽略本次調用,如果可以轉換為true,才有可能觸發回調函數。 if ( !locked ) { // 如果未傳入args,則使用默認值[] args = args || []; args = [ context, args.slice ? args.slice() : args ]; // 在可重復觸發、正在執行得列表上,重復觸發時,將上下文和參數放入數組queue中 queue.push( args ); // 回調函數列表不正在執行中,則調用工具函數fire()執行所有回調函數 if ( !firing ) { fire(); } } return this; }, // Call all the callbacks with the given arguments // 使用指定得參數調用回調函數列表中得所有回調函數 fire: function() { // 方法callbacks.fire()通過調用callbacks.fireWith(context,args)實現,調用時指定上下文為當前回調函數列表。 self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once // 判斷回調函數列表是否至少執行過一次 fired: function() { // 方法callbacks.fired()使用邏輯非運算符(!)對變量fired兩次取反,轉換未布爾值並返回。 return !!fired; } }; // 返回回調函數列表self return self; };