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');

 

posted @ 2013-12-11 01:39  lmule  阅读(1709)  评论(2编辑  收藏  举报