首页 > 代码库 > JQuery日记6.9 Promise/A之Callbacks

JQuery日记6.9 Promise/A之Callbacks

JQuery并没有简单的使用一个Array来存储回调函数,而是通过JQuery.Callbacks(options)返回一个self对象,此对象可以动态的add,remove和fire回调函数队列.此函数需要说明的是options参数,它是一个string,这个string由四个参数任意组合而成
options:
once:回调函数只执行一次
memory:调用add时触发回调函数使用fire时传入的参数.
unique:回调函数只能被添加到队列一次
stopOnFlase:有一个回调函数返回false,停止执行下面的回调函数.

JQuery.Callbacks(options)内的变量根据不同传参有不同的意思,这些函数由闭包引用扩大其生命周期,以在外部改变其值,以下进行说明
memory:如果options不包含‘memory‘字符串,memeory为false,其他为上一次fire(args)的参数.
fired:回调函数列表是否被触发过.
firing:回调函数列表是否正在被触发.为何要这个参数呢?因为如果在回调函数内调用了add,remove,fire 方法时会改变回调函数列表的状态(开始触发的位置,触发长度等等,这些是控制要执行回调函数列表中哪些函数的标志),此时其回调函数函数列表正在处于触发中,必须可以及时反映(修正触发位置,触发长度等等)回调函数列表的变化,以便在回调函数执行的循环中下一次迭代可以正确的触发回调函数列表.
firingStart:回调函数列表执行的开始位置.
firingLength:再执行几个回调函数
firingIndex:正在触发的函数在回调函数列表中的位置
list:存放回调函数
stack:如果在回调函数中调用fire或fireWith方法,将fire或fireWith传入的上下文和参数先入栈,稍后再用栈中的参数来作为参数执行回调函数中调用的fire或fireWidth
注:不解的是走到stack.push分支时是在回调函数里调用fire或fireWidth,但这样会导致死循环,这个stack到底怎么回事?还请明白的同学告知我一下!!
jQuery.Callbacks = function( options ) {

       // Convert options from String-formatted to Object-formatted if needed
       // (we check in cache first)
      options = typeof options === "string" ?
            ( optionsCache[ options ] || createOptions( options ) ) :
            jQuery.extend( {}, options );

       var // Last fire value (for non-forgettable lists)
            memory,
             // Flag to know if list was already fired
            fired,
             // Flag to know if list is currently firing
            firing,
             // First callback to fire (used internally by add and fireWith)
            firingStart,
             // End of the loop when firing
            firingLength,
             // Index of currently firing callback (modified by remove if needed)
            firingIndex,
             // Actual callback list
            list = [],
             // Stack of fire calls for repeatable lists
             // 如果没有配置once,stack为数组
             // stack存放重复时fire参数
            stack = !options.once && [],
             // Fire callbacks
            fire = function( data ) {
                   //memory 为false或fire时传入的参数
                  memory = options.memory && data;
                  fired = true;
                   //当前触发位置
                  firingIndex = firingStart || 0;
                   //触发回调函数队列的起始位置
                  firingStart = 0;
                   //待触发的回调函数队列长度
                  firingLength = list.length;
                   //正在触发标识设置为true
                   //只有在Callbacks的回调函数里此值才会为true
                  firing = true;
                   for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                         //若配置了stopOnFalse当回调函数队列中函数返回值为false
                         if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                               //memory设置为false
                              memory = false; // To prevent further calls using add
                               //不再执行后面的回调函数
                               break;
                        }
                  }
                   //回调函数执行结束
                  firing = false;
                   if ( list ) {
                         //未配置once
                         if ( stack ) {
                               //执行在回调函数里调用的fire
                               if ( stack.length ) {
                                    fire( stack.shift() );
                              }
                         //清空回调函数列表,但此callbacks仍可用
                        } else if ( memory ) {
                              list = [];
                         //让闭包的那些引用可以销毁
                        } else {
                              self.disable();
                        }
                  }
            },
             // Actual Callbacks object
            self = {
                   // Add a callback or a collection of callbacks to the list
                  add: function() {
                         if ( list ) {
                               // First, we save the current length
                               // 将添加回调函数之前将当前callbacks的长度设为执行开始
                               var start = list.length;
                              ( function add( args ) {
                                    jQuery.each( args, function( _, arg ) {
                                           var type = jQuery.type( arg );
                                           if ( type === "function" ) {
                                                 // 如果未设置unique或者回调函数列表里没有此函数
                                                 // 在回调函数队列里加上此函数
                                                 if ( !options.unique || !self.has( arg ) ) {
                                                      list.push( arg );
                                                }
                                           // 如果arg是个类数组,再递归add
                                          } else if ( arg && arg.length && type !== "string" ) {
                                                 // Inspect recursively
                                                add( arg );
                                          }
                                    });
                              })( arguments );
                               // Do we need to add the callbacks to the
                               // current firing batch?
                               //在正在触发的回调函数中调用add方法会走此分支
                               if ( firing ) {
                                     //立刻反应要触发回调函数长度的改变
                                    firingLength = list.length;
                               // With memory, if we're not firing then
                               // we should call right away
                              } else if ( memory ) {
                                     //改变触发的开始位置为未添加前的回调队列长度
                                    firingStart = start;
                                    fire( memory );
                              }
                        }
                         return this;
                  },
                   // Remove a callback from the list
                  remove: function() {
                         if ( list ) {
                              jQuery.each( arguments, function( _, arg ) {
                                     //arg所在list的索引
                                     var index;
                                     while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                           //删除
                                          list.splice( index, 1 );
                                           // Handle firing indexes
                                           //如果在回调函数里调用remove
                                           if ( firing ) {
                                                 //要删除的回调函数是还没触发的
                                                 if ( index <= firingLength ) {
                                                       //触发的长度减一
                                                       firingLength--;
                                                }
                                                 //要删除的是已经触发过的函数
                                                 if ( index <= firingIndex ) {
                                                       //正要触发索引减一
                                                       firingIndex--;
                                                }
                                          }
                                    }
                              });
                        }
                         return this;
                  },
                   // Check if a given callback is in the list.
                   // If no argument is given, return whether or not list has callbacks attached.
                  has: function( fn ) {
                         return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
                  },
                   // Remove all callbacks from the list
                  empty: function() {
                        list = [];
                        firingLength = 0;
                         return this;
                  },
                   // Have the list do nothing anymore
                   //是回调函数列表功能完全失效
                  disable: function() {
                        list = stack = memory = undefined;
                         return this;
                  },
                   // Is it disabled?
                  disabled: function() {
                         return !list;
                  },
                   // Lock the list in its current state
                   //锁住当前的状态,不会执行在回调函数里调用fire或fireWidth
                  lock: function() {
                        stack = undefined;
                         if ( ! memory ) {
                               self.disable();
                        }
                         return this;
                  },
                   // Is it locked?
                  locked: function() {
                         return !stack;
                  },
                   // Call all callbacks with the given context and arguments
                  fireWith: function( context, args ) {
                         //没有被触发或者可以重复触发
                         if ( list && ( !fired || stack ) ) {
                              args = args || [];
                              args = [ context, args.slice ? args.slice() : args ];
                               //?其实这里有一点不解,走到这个分
                               //是在在回调函数里调通fire或fireWidth,但这样会导致死循环
                               if ( firing ) {
                                    stack.push( args );
                              } else {
                                    fire( args );
                              }
                        }
                         return this;
                  },
                   // Call all the callbacks with the given arguments
                  fire: function() {
                        self.fireWith( this, arguments );
                         return this;
                  },
                   // To know if the callbacks have already been called at least once
                  fired: function() {
                         return !!fired;
                  }
            };

       return self;
};