• 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');
  • 相关阅读:
    [翻译] SVProgressHUD
    使用CoreData [4]
    Android学习笔记之AndroidManifest.xml文件解析
    Android 版本自动更新
    两个android程序间的相互调用(apk互调)
    Android 生成含签名文件的apk安装包
    【已解决】Android ADT中增大AVD内存后无法启动:emulator failed to allocate memory 8
    android adt与android sdk有什么关系,他们在开发中各起到什么作用
    Please ensure that adb is correctly located at……问题解决方案
    安装Android SDK时,点击SDK Manager.exe闪退,并且jdk的环境变量是对的。
  • 原文地址:https://www.cnblogs.com/lmule/p/3468559.html
Copyright © 2020-2023  润新知