在上一篇jQuery.Callbacks之demo主要說了Callbacks對象初始化常見的選項,這一篇主要分析下Callbacks對象的源代碼,對給出兩個較爲繁瑣的demohtml
1 // String to Object options format cache 2 var optionsCache = {}; 3 4 // Convert String-formatted options into Object-formatted ones and store in cache 5 /* 6 這個函數主要將傳入的options字符串封裝成對象 7 好比將傳入的'once memory'封裝成 8 optionsCache['once memory'] = { 9 once : true, 10 memory : true 11 } 12 這樣方便下次一樣的options複用和判斷 13 */ 14 function createOptions( options ) { 15 var object = optionsCache[ options ] = {}; 16 jQuery.each( options.split( core_rspace ), function( _, flag ) { 17 object[ flag ] = true; 18 }); 19 return object; 20 } 21 22 /* 23 * Create a callback list using the following parameters: 24 * 25 * options: an optional list of space-separated options that will change how 26 * the callback list behaves or a more traditional option object 27 * 28 * By default a callback list will act like an event callback list and can be 29 * "fired" multiple times. 30 * 31 * Possible options: 32 * 33 * once: will ensure the callback list can only be fired once (like a Deferred) 34 * 35 * memory: will keep track of previous values and will call any callback added 36 * after the list has been fired right away with the latest "memorized" 37 * values (like a Deferred) 38 * 39 * unique: will ensure a callback can only be added once (no duplicate in the list) 40 * 41 * stopOnFalse: interrupt callings when a callback returns false 42 * 43 */ 44 jQuery.Callbacks = function( options ) { 45 46 // Convert options from String-formatted to Object-formatted if needed 47 // (we check in cache first) 48 options = typeof options === "string" ? 49 ( optionsCache[ options ] || createOptions( options ) ) : 50 jQuery.extend( {}, options ); 51 52 var // Last fire value (for non-forgettable lists) 53 //大多數狀況下這個變量是包含兩個元素的數組,[0]表示上次調用的對象,[1]表示上次調用的參數 54 memory, 55 // Flag to know if list was already fired 56 //標識是否執行過回調函數,主要用來實現once 57 fired, 58 // Flag to know if list is currently firing 59 //當前是否在firing,能夠參考多線編程中鎖的概念,主要用在調用回調函數時,對callbacks對象進行add、remove或者fire,後面會有兩個單獨的例子說明這種狀況 60 firing, 61 // First callback to fire (used internally by add and fireWith) 62 firingStart, 63 // End of the loop when firing 64 firingLength, 65 // Index of currently firing callback (modified by remove if needed) 66 firingIndex, 67 // Actual callback list 68 //全部的回調會被push到這個數組 69 list = [], 70 // Stack of fire calls for repeatable lists 71 //結合firing使用,若是有once選項沒什麼做用,不然當firing爲true時將add或者fire的操做臨時存入這個變量,以便於循環完list時繼續處理這個變量裏面的函數隊列 72 stack = !options.once && [], 73 // Fire callbacks 74 fire = function( data ) { 75 //若是設置memory爲true,則將本次的參數data緩存到memory中,用於下次調用 76 memory = options.memory && data; 77 fired = true; 78 //若是options.memory爲true,firingStart爲上一次Callbacks.add後回調列表的length值 79 firingIndex = firingStart || 0; 80 firingStart = 0; 81 firingLength = list.length; 82 firing = true; 83 for ( ; list && firingIndex < firingLength; firingIndex++ ) { 84 //若是stopOnFalse爲true且本次執行的回調函數返回值爲false,則終止回調函數隊列的執行 85 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { 86 //設置memory爲false,防止調用add時會被fire(這個分支是在stopOnFalse memory時被觸發) 87 memory = false; // To prevent further calls using add 88 break; 89 } 90 } 91 firing = false; 92 if ( list ) { 93 //options.once爲false(stack的做用見上) 94 if ( stack ) { 95 //存在遞歸的可能,因此不用使用while 96 if ( stack.length ) { 97 fire( stack.shift() ); 98 } 99 //memory = true, memory = true的狀況 100 } else if ( memory ) { 101 list = []; 102 } else { 103 //once = true, memory = false的狀況 104 self.disable(); 105 } 106 } 107 }, 108 // Actual Callbacks object 109 self = { 110 // Add a callback or a collection of callbacks to the list 111 add: function() { 112 if ( list ) { 113 // First, we save the current length 114 var start = list.length; 115 (function add( args ) { 116 jQuery.each( args, function( _, arg ) { 117 var type = jQuery.type( arg ); 118 if ( type === "function" ) { 119 //實現unique(回調不惟一 或 惟一且不存在,則push) 120 if ( !options.unique || !self.has( arg ) ) { 121 list.push( arg ); 122 } 123 //若是arg是數組,遞歸添加回調 124 } else if ( arg && arg.length && type !== "string" ) { 125 // Inspect recursively 126 add( arg ); 127 } 128 }); 129 })( arguments ); 130 // Do we need to add the callbacks to the 131 // current firing batch? 132 if ( firing ) { 133 firingLength = list.length; 134 // With memory, if we're not firing then 135 // we should call right away 136 //若是memory不是false,則直接每次add的時候都自動fire 137 } else if ( memory ) { 138 firingStart = start; 139 fire( memory ); 140 } 141 } 142 return this; 143 }, 144 // Remove a callback from the list 145 remove: function() { 146 if ( list ) { 147 jQuery.each( arguments, function( _, arg ) { 148 var index; 149 while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { 150 list.splice( index, 1 ); 151 // Handle firing indexes 152 //若是在執行Callbacks.remove操做的狀態爲firing時則更新firingLength和firingIndex的值 153 if ( firing ) { 154 if ( index <= firingLength ) { 155 firingLength--; 156 } 157 //特殊處理,若是移除的回調的索引小於當前正在執行回調的索引,則firingIdex-- 158 //後面未執行的回調則得以正常執行 159 if ( index <= firingIndex ) { 160 firingIndex--; 161 } 162 } 163 } 164 }); 165 } 166 return this; 167 }, 168 // Control if a given callback is in the list 169 has: function( fn ) { 170 return jQuery.inArray( fn, list ) > -1; 171 }, 172 // Remove all callbacks from the list 173 empty: function() { 174 list = []; 175 return this; 176 }, 177 // Have the list do nothing anymore 178 disable: function() { 179 list = stack = memory = undefined; 180 return this; 181 }, 182 // Is it disabled? 183 disabled: function() { 184 return !list; 185 }, 186 // Lock the list in its current state 187 lock: function() { 188 stack = undefined; 189 if ( !memory ) { 190 self.disable(); 191 } 192 return this; 193 }, 194 // Is it locked? 195 locked: function() { 196 return !stack; 197 }, 198 // Call all callbacks with the given context and arguments 199 fireWith: function( context, args ) { 200 args = args || []; 201 args = [ context, args.slice ? args.slice() : args ]; 202 if ( list && ( !fired || stack ) ) { 203 if ( firing ) { 204 stack.push( args ); 205 } else { 206 fire( args ); 207 } 208 } 209 return this; 210 }, 211 // Call all the callbacks with the given arguments 212 fire: function() { 213 self.fireWith( this, arguments ); 214 return this; 215 }, 216 // To know if the callbacks have already been called at least once 217 fired: function() { 218 return !!fired; 219 } 220 }; 221 222 return self; 223 };
須要特殊注意的是有一個firing這個變量,下面給出這個變量的應用場景:編程
一、在Callbacks.add中firing爲true的狀況數組
1 // 定義三個將要增長到回調列表的回調函數fn1,fn2,fn3 2 function fn1(val){ 3 console.log( 'fn1 says ' + val ); 4 //此時Callbacks函數內部的firingLength會自動加1,雖然初始化的Callbacks對象有memory選項, 5 //但add並不會當即執行fn2,而是等執行完add前的函數隊列以後再執行fn2 6 cbs.add(fn2); 7 } 8 function fn2(val){ 9 console.log( 'fn2 says ' + val ); 10 } 11 function fn3(val){ 12 console.log( 'fn3 says ' + val ); 13 } 14 15 // Callbacks傳遞了memory 16 // 也能夠這樣使用$.Callbacks({ memory: true }); 17 var cbs = $.Callbacks('memory'); 18 19 // 將fn1增長到回調列表中,由於在fn1中有執行了add(fn2)操做,所以回調列表中的回調爲fn1,fn2 20 cbs.add(fn1); 21 22 //fn1 says foo 23 //fn2 says foo 24 cbs.fire('foo'); 25 26 //將以前fire的參數傳遞給最近增長的回調fn3,並執行fn3 27 //fn3 says foo 28 cbs.add(fn3); 29 30 //再執行一次fire,注意此時回調列表中的回調依次是fn1,fn2,fn3,fn2 31 //fn1 says bar 32 //fn2 says bar 33 //fn3 says bar 34 //fn2 says bar 35 cbs.fire('bar');
二、在Callbacks.fireWith中firing爲true的狀況緩存
function fn1(val){ console.log( 'fn1 says ' + val ); } function fn2(val){ console.log( 'fn2 says ' + val ); //此時並不會當即觸發cbs裏面的回調,而是先把[window, ['bar']]放入stack裏面 //等執行完fireWith前的函數隊列以後才執行 cbs.fireWith(window, ['bar']); //firingLength會減一,必定要將當前的函數remove掉,不然會致使死循環 cbs.remove(fn2); } var cbs = $.Callbacks(); cbs.add(fn1); cbs.add(fn2); //fn1 says bar //fn2 says bar //fn1 says bar cbs.fire('bar');