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     };
View Code

 

 

用法:

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!
*/

 

posted @ 2013-05-13 16:42  LukeLin  阅读(939)  评论(0编辑  收藏  举报