jQuery.Callbacks之源碼解讀

  在上一篇jQuery.Callbacks之demo主要說了Callbacks對象初始化常見的選項,這一篇主要分析下Callbacks對象的源代碼,對給出兩個較爲繁瑣的demohtml

 

  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這個變量,下面給出這個變量的應用場景:編程

  一、在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');

  二、在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');
相關文章
相關標籤/搜索