jQuery.Callbacks之源码解读
在上一篇jQuery.Callbacks之demo主要说了Callbacks对象初始化常见的选项,这一篇主要分析下Callbacks对象的源代码,对给出两个较为繁琐的demo
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这个变量,下面给出这个变量的应用场景:
1、在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');
2、在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');