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 args218 * @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:foofn2 says:foobarfn2 says:barfoobar*/$.Callbacks( 'unique' ):var callbacks = $.Callbacks( "unique" );callbacks.add( fn1 );callbacks.fire( "foo" );callbacks.add( fn1 ); // repeat additioncallbacks.add( fn2 );callbacks.fire( "bar" );callbacks.remove( fn2 );callbacks.fire( "foobar" );/*output:foobarfn2 says:barfoobar*/$.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:foobarfoobar*/因为$.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 additioncallbacks.add( fn2 );callbacks.fire( "bar" );callbacks.add( fn2 );callbacks.fire( "baz" );callbacks.remove( fn2 );callbacks.fire( "foobar" );/*output:foofn2 says:foobarfn2 says:barbazfn2 says:bazfoobar*/使用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!*/