/* * once: 確保回調列表僅只fire一次 * unique: 在執行add操做中,確保回調列表中不存在重複的回調 * stopOnFalse: 當執行回調返回值爲false,則終止回調隊列的執行 * momery: 記錄上一次fire時的參數,並在add中傳遞給fire和執行fire,執行時firingIndex爲上一次fire時的firingLength */
var optionsCache = {}, // Used for splitting on whitespace core_rnotwhite = /\S+/g; // Convert String-formatted options into Object-formatted ones and store in cache function createOptions( options ) { // 多個變量指向同一對象(或數組)引用時,其中一個變量修改了被引用對象的內部結構,其餘引用變量也會表現出來 var object = optionsCache[ options ] = {}; jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; // optionsCache[ options ][ flag ] = true; }); return object; } 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 ] ( optionsCache[ options ] || createOptions( options ) ) : // 說明也能夠這樣$.Callbacks({once:true, memory:true})使用 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 stack = !options.once && [], // Fire callbacks // data爲fireWith內部整理的args數組 fire = function( data ) { memory = options.memory && data; fired = true; // 處理在add中,options.memory = true;的狀況 firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; firing = true; for ( ; list && firingIndex < firingLength; firingIndex++ ) { // 正在執行的回調返回值爲false 且 options.stopOnFalse爲true,則終止回調隊列的執行 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { memory = false; // To prevent further calls using add break; } } firing = false; if ( list ) { // 處理正在執行的回調中執行fireWith的操做; if ( stack ) { if ( stack.length ) { fire( stack.shift() ); } } // 上一分支狀態爲回調執行過,且能夠執行屢次 // 此時 options.once = true; 這裏將list設置爲[],只是確保下次執行fire時,無回調執行 // 可是若是 options.memory = true; 仍然會執行add中的fire操做,由於此時回調列表中已有回調 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" ) { // 回調不惟一 或 惟一且不存在,則push 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? // 正在執行的回調執行了add操做,則更新firingLength if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away // 若是options.memory爲true,則再次執行fire,且參數相同,fire中的firingIndex爲此時的firingStart } 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 // 正在執行的回調執行了remvoe操做,則更新firingLength和firingIndex的值 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 = []; return this; }, // Have the list do nothing anymore // 禁用add,remove,fire主要方法的工做 disable: function() { list = stack = memory = undefined; return this; }, // Is it disabled? disabled: function() { return !list; }, // Lock the list in its current state lock: function() { // 若是回調執行過,則將阻止self.fire操做 // 但若是 options.memory = true,則仍然會執行fire操做 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 ) ) { // 正在執行的回調函數執行了fireWith操做( 暗指回調列表已執行過,且能夠執行屢次,stack = []; ) // 該函數須要條件執行,或有移除該函數的操做,不然陷入死循環,詳見例2 if ( firing ) { stack.push( args ); } // 正在執行的回調函數沒有執行fireWith操做 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; };
例1:數組
$(function(){ // 定義三個將要增長到回調列表的回調函數fn1,fn2,fn3 function fn1(arg){ console.log( 'fn1 says:' + arg ); // 在fn1中執行Callbacks.add操做,此時Callbacks函數內部的firingLength將會獲得更新 $callbacks.add(fn2); } function fn2(arg){ console.log( 'fn2 says:' + arg ); } function fn3(arg){ console.log( 'fn3 says:' + arg ); } // Callbacks傳遞了memory // 也能夠這樣使用$.Callbacks({ memory: true }); var $callbacks = $.Callbacks('memory'); // 將fn1增長到回調列表中,由於在fn1中有執行了add(fn2)操做,所以回調列表中的回調爲fn1,fn2 $callbacks.add(fn1); // output: fn1 says:foo // output: fn2 says:foo $callbacks.fire('foo'); // 將以前fire的參數傳遞給最近增長的回調fn3,並執行fn3 // output: fn3 says:foo $callbacks.add(fn3); // 再執行一次fire,注意此時回調列表中的回調一次是fn1,fn2,fn3,fn2 // output: fn1 says:baz // output: fn2 says:baz // output: fn3 says:baz // output: fn2 says:baz // 若是指望回調列表中只有fn1,fn2,fn3,只需在Callbacks函數中傳入unique $callbacks.fire('baz'); });
例2緩存
$(function(){ function fn1(arg){ console.log( 'fn1 says:' + arg ); } function fn2(arg){ console.log( 'fn2 says:' + arg ); $callbacks.fireWith(window, ['yjh']); // 必定要執行這一步,不然將會陷入死循環 $callbacks.remove(fn2); } var $callbacks = $.Callbacks(); $callbacks.add(fn1); // output: fn1 says:foo $callbacks.fire('foo'); $callbacks.add(fn2); // output: fn1 says:baz // output: fn2 says:baz // output: fn1 says:yjh $callbacks.fire('baz'); });
PS:app
此前寫過一篇關於jQuery.Callbacks源碼分析的隨筆,理解不透徹,今天又從新翻閱了一下,記錄一下本身的源碼閱讀,相比以前,感受好多了。ide
閱讀前,能夠先看API,弄清楚四個參數標誌,'once', 'memory', 'unique', 'stopOnFalse', 簡單的執行add, fire操做,而後再看源碼;函數
閱讀順序:oop
一、先閱讀var聲明的變量,fire函數的前半部分,self對象中的add, remove函數,有些難以理解暫時往下看;源碼分析
二、而後閱讀self對象中的fire,fireWith,最後再來閱讀fire函數,弄清楚後再看其餘self對象中的方法。this