jQuery的回调管理机制

// 对option的一个缓存,避免每次都需要createOptions,option是创建Callback对象时的传入的参数
// 每个option被存入optionsCache中类似于{memory:true, once:true,...}
var optionsCache = {};

// 将字符串表达转成对象表达,并存在缓存中,一个内部使用的方法。
function createOptions( options ) {
  var object = optionsCache[ options ] = {};
  jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
    object[ flag ] = true;
  });
  return object;
}

/*

 * Callback构造函数在构造对象时的一些参数选项

 * 
 * once: 确保这个回调列表只执行一次(像一个递延 Deferred).
 * memory: 保持以前的值和将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred).
 * unique: 确保一次只能添加一个回调(不能列表中的重复).
 * stopOnFalse: 当一个回调返回false 时中断调用

*/


// options参数以字符串的形式传入,例如"unique memory",以空格分割,用来控制回调函数的行为
// $.Callbacks是在jQuery内部使用,如为$.ajax,$.Deferred等组件提供基础功能的函数。它也可以用在类似功能的一些组件中,如自己开发的插件。
jQuery.Callbacks = function( options ) {

  options = typeof options === "string" ?
  // 先检测一下cache,如果为空,则把options从字符串的形式转变为Object的形式
  // 通过字符串在optionsCache寻找有没有相应缓存,如果没有则创建一个,有则引用
  ( optionsCache[ options ] || createOptions( options ) ) :
  // 如果是对象则通过jQuery.extend扩展到新对象中赋值给options
  jQuery.extend( {}, options );

  var 
  // 最后一次触发回调时传的参数
  memory,
  // 列表中的函数是否已经回调至少一次
  fired,
  // 列表中的函数是否正在回调中 
  firing,
  // 回调的起点
  firingStart,
  // 回调时的循环结尾
  firingLength,
  // 当前正在回调的函数索引
  firingIndex,
  // 回调函数列表
  list = [],
  // 可重复的回调函数堆栈,用于控制触发回调时的参数列表
  stack = !options.once && [],
  // 触发回调函数列表

  //这里的data参数到底是啥???
  fire = function( data ) {
    //如果参数memory为true,则记录data
    memory = options.memory && data;
    //标记触发回调
    fired = true;
    firingIndex = firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    //标记正在触发回调
    firing = true;
    for ( ; list && firingIndex < firingLength; firingIndex++ ) {
      // 对当前回调函数在data[0]环境下,传入data[1]参数,并执行,如果返回false,并且设置了stopOnFalse,则将memory设为false并返回。
      if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {

        // 阻止未来可能由于add所产生的回调,因为当memory为true时,add进来的回调函数会立即被触发
        memory = false; 

        //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环
        break;
      }
    }
    //标记回调结束
    firing = false;
    //如果列表存在
    if ( list ) {
      //如果堆栈存在
      if ( stack ) {
        //如果堆栈不为空
        if ( stack.length ) {
          //从堆栈头部取出,递归fire。
          fire( stack.shift() );
        }
      //否则,如果有记忆
      } else if ( memory ) {
        //列表清空
        list = [];
      //再否则阻止回调列表中的回调
      } else {
        self.disable();
      }
    }
  },
  //  Callback构造函数返回的对象
  self = {
    // 向回调列表中添加一个回调或回调的集合。
    add: function() {
      if ( list ) {
        // 首先我们存储当前列表长度 主要用于当Callbacks传入memory参数时
        var start = list.length;
        // 在做其它操作之前,先对args做一些处理
        (function add( args ) {
          // 对args传进来的列表的每一个对象执行操作
          jQuery.each( args, function( _, arg ) {
            var type = jQuery.type( arg );
            // 如果是函数
            if ( type === "function" ) {
              // 如果回调离别不允许重复,则看看列表中是否包含,如果不包含则加入列表
              if ( !options.unique || !self.has( arg ) ) {
                list.push( arg );
              }
            // 如果是类数组或对象,则递归调用add方法
            } else if ( arg && arg.length && type !== "string" ) {
              //递归 调用add方法往list中增加回调
              add( arg );
            }
          });
        })( arguments );
        // 如果整个callbacks对象中的callback正在执行时,回调时的循环结尾变成add之后的list长度长度,确保新增加的回调函数会被执行到
        if ( firing ) {
          firingLength = list.length;
        // 如果有memory,我们立刻调用。 这里的memory
        } else if ( memory ) {
          firingStart = start;
          fire( memory );
        }
      }
      return this;
    },
    // 从列表删除回调函数
    remove: function() {
      if ( list ) {
        //继续用jQuery.each,对arguments中的所有参数处理
        jQuery.each( arguments, function( _, arg ) {
          var index;
          //找到arg在列表中的位置
          while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
            //根据得到的位置删除列表中的回调函数
            list.splice( index, 1 );
            if ( firing ) {
              //如果正在回调过程中,则调整循环的索引和长度
              if ( index <= firingLength ) {
                firingLength--;
              }
              if ( index <= firingIndex ) {
                firingIndex--;
              }
            }
          }
        });
      }
      return this;
    },
    // 判断回调函数是否在列表中
    has: function( fn ) {
      return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
    },
    // 从列表中删除所有回调函数
    empty: function() {
      list = [];
      firingLength = 0;
      return this;
    },
    // 禁用回调列表中的回调。禁用之后无法再向列表中加入回调函数
    disable: function() {
      list = stack = memory = undefined;
      return this;
    },
    // 判断列表是否被禁用
    disabled: function() {
      return !list;
    },
    // 锁定列表
    lock: function() {
      stack = undefined;
      if ( !memory ) {
        self.disable();
      }
      return this;
    },
    // 判断列表是否被锁
    locked: function() {
      return !stack;
    },
    // 以给定的上下文和参数调用所有回调函数
    fireWith: function( context, args ) {
      if ( list && ( !fired || stack ) ) {
        args = args || [];
        args = [ context, args.slice ? args.slice() : args ];
        //如果正在回调
        if ( firing ) {
          //将参数推入堆栈,等待当前回调结束再调用
          stack.push( args );
          //否则直接调用
        } else {
          fire( args );
        }
      }
      return this;
    },
    // 以给定的参数调用所有回调函数
    fire: function() {
      self.fireWith( this, arguments );
      return this;
    },
    // 回调函数列表是否至少被调用一次
    fired: function() {
      return !!fired;
    }
  };

  return self;
};

原文地址:https://www.cnblogs.com/charling/p/3454888.html