jQuery1.9.1源码分析--Callbacks对象
1 var optionsCache = {}; 2 3 /* 4 根据字符串格式的参数创建对象的键值对象, 5 并且返回一个object变量存储已经存在的key参数,且value值为true, 6 与optionsCache引用同一个对象 7 */ 8 9 function createOptions(options) { 10 var object = optionsCache[options] = {}; 11 jQuery.each(options.match(core_rnotwhite) || [], function (_, flag) { 12 object[flag] = true; 13 }); 14 return object; 15 } 16 17 /* 18 * Create a callback list using the following parameters: 19 * 20 * options: an optional list of space-separated options that will change how 21 * the callback list behaves or a more traditional option object 22 * 23 * By default a callback list will act like an event callback list and can be 24 * "fired" multiple times. 25 * 26 * Possible options: 27 * 28 * once: will ensure the callback list can only be fired once (like a Deferred) 29 * 确保这个回调列表只执行一次. 30 * 31 * 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) 32 * 当回调列表已经被触发调用了,我们再给列表添加回调的时候将会执行该回调 33 * 34 * unique: will ensure a callback can only be added once (no duplicate in the list) 35 * 确保一个回调再列表中只会被添加一次(即列表不能有重复的回调). 36 * 37 * stopOnFalse: interrupt callings when a callback returns false 38 * 当一个回调返回false 时中断调用 39 */ 40 jQuery.Callbacks = function (options) { 41 // 将options字符串格式转换为对象格式 42 // 先检查是否已有缓存 43 options = typeof options === 'string' ? 44 (optionsCache[options] || createOptions(options)) : 45 jQuery.extend({}, options); 46 47 var 48 // 用来标识列表是否正在触发 49 firing, 50 // 上一次触发的值 (备忘列表) 51 memory, 52 // 列表已被触发的标识 53 fired, 54 // 回调列表的长度 55 firingLength, 56 // 当前触发的回调索引值 57 firingIndex, 58 // 第一个要触发的回调函数 59 // (used internally by add and fireWith) 60 firingStart, 61 // 回调列表 62 list = [], 63 // 可重复的回调函数堆栈,用于控制触发回调时的参数列表 64 // flags不能为once 65 stack = !options.once && [], 66 // 触发回调方法,结束了当前队列, 67 // 如果还有其他等待队列,则也触发 68 fire = function (data) { 69 // 如果flags包含memory,则记录data 70 // 值是一个数组第一个元素是fireWith的context对象,第二个则是fire方法的参数伪数组 71 memory = options.memory && data; 72 // 标记已触发 73 fired = true; 74 firingIndex = firingStart || 0; 75 firingStart = 0; 76 firingLength = list.length; 77 // 标记正在触发回调 78 firing = true; 79 // 遍历回调列表 80 for (; list && firingIndex < firingLength; firingIndex++) { 81 if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) { 82 // 强制将memory设置为false 83 // 阻止未来可能由于add所产生的回调 84 memory = false; 85 //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环 86 break; 87 } 88 } 89 // 标记回调结束 90 firing = false; 91 // 如果列表存在 92 if (list) { 93 // 如果堆栈存在(非once的情况) 94 if (stack) { 95 // 如果堆栈不为空 96 if (stack.length) { 97 // 从堆栈头部取出,递归fire 98 fire(stack.shift()); 99 } 100 101 // 否则,如果有记忆(memory && ((once && unique) || once)) 102 } else if (memory) { 103 // 列表清空 104 list = []; 105 106 // 再否则阻止回调列表中的回调 (once || (once && unique)) 107 } else { 108 self.disable(); 109 } 110 } 111 }, 112 // 暴露在外的Callbacks对象 113 self = { 114 /** 115 * 回调列表中添加一个回调或回调的集合。 116 * {arguments} 一个函数,或者一个函数数组用来添加到回调列表 117 * @returns {*} 118 */ 119 add: function () { 120 if (list) { 121 // 首先存储当前列表长度 122 var start = list.length; 123 (function add(args) { 124 jQuery.each(args, function (_, arg) { 125 var type = jQuery.type(arg); 126 // 如果是函数 127 if (type === 'function') { 128 // 确保是否可以重复或者没有该回调 129 if (!options.unique || !self.has(arg)) { 130 list.push(arg); 131 } 132 133 // 如果是类数组或对象 134 } else if (arg && arg.length && type !== 'string') { 135 // 递归 136 add(arg); 137 } 138 }); 139 })(arguments); 140 141 // 如果正在回调就将回调时的循环结尾变成现有长度 142 if (firing) { 143 firingLength = list.length; 144 145 // 否则如果有memory,我们立刻调用 146 // 前面至少有一次fire,这样memory才会有值 147 } else if (memory) { 148 firingStart = start; 149 fire(memory); 150 } 151 } 152 153 return this; 154 }, 155 /* 156 删除回调或回调回调列表的集合 157 */ 158 remove: function () { 159 if (list) { 160 jQuery.each(arguments, function (_, arg) { 161 var index; 162 // 找到arg在列表中的位置 163 while ((index = jQuery.inArray(arg, list, index)) > -1) { 164 // 根据得到的位置删除列表中的回调函数 165 list.splice(index, 1); 166 167 // 如果正在回调过程中,则调整循环的索引和长度 168 // 继续下次循环 169 if (firing) { 170 if (index <= firingLength) { 171 firingLength--; 172 } 173 if (index <= firingIndex) { 174 firingIndex--; 175 } 176 } 177 } 178 }); 179 } 180 181 return this; 182 }, 183 // 回调函数是否在列表中 184 has: function (fn) { 185 return fn ? jQuery.inArray(fn, list) > -1 : !!(list && list.length); 186 }, 187 // 从列表中删除所有回调函数 188 empty: function () { 189 list = []; 190 return this; 191 }, 192 /* 193 禁用回调列表中的回调 194 */ 195 disable: function () { 196 list = stack = memory = undefined; 197 return this; 198 }, 199 // 判断是否被禁用了 200 disabled: function () { 201 return !list; 202 }, 203 // 锁定列表 204 lock: function () { 205 stack = undefined; 206 if (!memory) { 207 self.disable(); 208 } 209 return this; 210 }, 211 locked: function () { 212 return !stack; 213 }, 214 /** 215 * 以给定的上下文和参数调用所有回调函数 216 * @param context 上下文 217 * @param args 218 * @returns {*} 219 */ 220 fireWith: function (context, args) { 221 args = args || []; 222 args = [context, args.slice ? args.slice() : args]; 223 224 if (list && (!fired || stack)) { 225 // 如果正在回调 226 if (firing) { 227 // 将参数推入堆栈,等待当前回调结束再调用 228 stack.push(args); 229 230 // 否则直接调用 231 } else { 232 fire(args); 233 } 234 } 235 236 return this; 237 }, 238 // 以给定的参数调用所有回调函数 239 fire: function () { 240 self.fireWith(this, arguments); 241 return this; 242 }, 243 // 回调列表是否被触发过 244 fired: function () { 245 return !!fired; 246 } 247 }; 248 249 return self; 250 };
用法:
function fn1( value ){ console.log( value ); } function fn2( value ){ fn1("fn2 says:" + value); return false; } var callbacks = $.Callbacks(); callbacks.add( fn1 ); callbacks.fire( "foo!" ); // outputs: foo! callbacks.add( fn2 ); callbacks.fire( "bar!" ); // outputs: bar!, fn2 says: bar! 可用的 flags: once: 确保这个回调列表只执行一次(像一个递延 Deferred). memory: 保持以前的值和将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred). unique: 确保一次只能添加一个回调(所以有没有在列表中的重复). stopOnFalse: 当一个回调返回false 时中断调用 默认情况下,回调列表将像事件的回调列表中可以多次触发。 如何在理想情况下应该使用的flags的例子,见下文: $.Callbacks( 'once' ): var callbacks = $.Callbacks( "once" ); callbacks.add( fn1 ); callbacks.fire( "foo" ); callbacks.add( fn2 ); callbacks.fire( "bar" ); callbacks.remove( fn2 ); callbacks.fire( "foobar" ); /* output: foo */ $.Callbacks( 'memory' ): var callbacks = $.Callbacks( "memory" ); callbacks.add( fn1 ); callbacks.fire( "foo" ); callbacks.add( fn2 ); callbacks.fire( "bar" ); callbacks.remove( fn2 ); callbacks.fire( "foobar" ); /* output: foo fn2 says:foo bar fn2 says:bar foobar */ $.Callbacks( 'unique' ): var callbacks = $.Callbacks( "unique" ); callbacks.add( fn1 ); callbacks.fire( "foo" ); callbacks.add( fn1 ); // repeat addition callbacks.add( fn2 ); callbacks.fire( "bar" ); callbacks.remove( fn2 ); callbacks.fire( "foobar" ); /* output: foo bar fn2 says:bar foobar */ $.Callbacks( 'stopOnFalse' ):function fn1( value ){ console.log( value ); return false; } function fn2( value ){ fn1("fn2 says:" + value); return false; } var callbacks = $.Callbacks( "stopOnFalse"); callbacks.add( fn1 ); callbacks.fire( "foo" ); callbacks.add( fn2 ); callbacks.fire( "bar" ); callbacks.remove( fn2 ); callbacks.fire( "foobar" ); /* output: foo bar foobar */ 因为$.Callbacks() 支持一个列表的flags而不仅仅是一个,设置几个flags,有一个累积效应,类似“&&”。这意味着它可能结合创建回调名单,unique 和确保如果名单已经触发,将有更多的回调调用最新的触发值 (i.e.$.Callbacks("unique memory")). $.Callbacks( 'unique memory' ):function fn1( value ){ console.log( value ); return false; } function fn2( value ){ fn1("fn2 says:" + value); return false; } var callbacks = $.Callbacks( "unique memory" ); callbacks.add( fn1 ); callbacks.fire( "foo" ); callbacks.add( fn1 ); // repeat addition callbacks.add( fn2 ); callbacks.fire( "bar" ); callbacks.add( fn2 ); callbacks.fire( "baz" ); callbacks.remove( fn2 ); callbacks.fire( "foobar" ); /* output: foo fn2 says:foo bar fn2 says:bar baz fn2 says:baz foobar */ 使用Callbacks实现的观察者模式: var topics = {}; jQuery.Topic = function( id ) { var callbacks, method, topic = id && topics[ id ]; if ( !topic ) { callbacks = jQuery.Callbacks(); topic = { publish: callbacks.fire, subscribe: callbacks.add, unsubscribe: callbacks.remove }; if ( id ) { topics[ id ] = topic; } } return topic; }; // Subscribers $.Topic( "mailArrived" ).subscribe( fn1 ); $.Topic( "mailArrived" ).subscribe( fn2 ); $.Topic( "mailSent" ).subscribe( fn1 ); // Publisher $.Topic( "mailArrived" ).publish( "hello world!" ); $.Topic( "mailSent" ).publish( "woo! mail!" ); // Here, "hello world!" gets pushed to fn1 and fn2 // when the "mailArrived" notification is published // with "woo! mail!" also being pushed to fn1 when // the "mailSent" notification is published. /* output: hello world! fn2 says: hello world! woo! mail! */