jQuery.Callbacks 一个函数管理器

1829 查看

源码:


var rnotwhite = (/\S+/g); // String to Object options format cache var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; } // 上面的代码是用来将String的参数装换成对象形式 如:'unique memory' 转成 {unique:true,memory:true} /* * Create a callback list using the following parameters: * * options: an optional list of space-separated options that will change how * the callback list behaves or a more traditional option object * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible options: * * once: will ensure the callback list can only be fired once (like a Deferred) * 设置memory就是保存上下文使得后续添加进来的函数能够立即执行且用之前保存的上下文和参数 * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ //Callback 中这段代码主要理解各个选项的作用 和 fire add fireWith 这三个函数基本就明白怎么回事了。 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 stack = !options.once && [], // Fire callbacks fire = function( data ) { memory = options.memory && data; fired = true; firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; firing = true; for ( ; list && firingIndex < firingLength; firingIndex++ ) { //这里的data[0]是指上下文context data[1] 是指参数数组 看下fireWith函数就知道了 //但选项中有stopOnFalse的时候 函数如果返回false就停止执行队列中后续的回调函数 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { memory = false; // To prevent further calls using add break; } } //执行完后将firing设置成false表示已经不是正在执行了 firing = false; if ( list ) { //这里的stack存放的是后续调用fireWith或者fire的上下文和参数 可以看fireWith中stack //如果正在执行callback队列中的函数即firing为true时 这时再调用fireWith或者fire就会放在stack中排队 //个人认为这个变量应该取名为queue好,因为是先进先出的队列结构 if ( stack ) { if ( stack.length ) { fire( stack.shift() ); } } else if ( memory ) { //这里如果stack为false的话 那么说明设置了once, stack = !options.once && [] 这里 //可以看出来 这里就相当于调用了$.Callback('once 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 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 ); } } else if ( arg && arg.length && type !== "string" ) { // Inspect recursively add( arg ); } }); })( arguments ); // Do we need to add the callbacks to the // current firing batch? //看是回调函数列表是否正在触发 如果是那么只需要修改最后的长度就行了 //因为这样到时会调用到刚添加进去的回调函数 if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away //如果list已经执行完fired 为真 那么查看选项中memory是否设置 如果设置那么立即执行 //刚add的回调函数 } else if ( memory ) { //前面的start在这里起作用了。如果队列不正在执行的话.即fired = true 时执行刚添加的函数 //memory 作为上下文 memory保存了前面fire过的上下文 firingStart = start; fire( memory ); } } return this; }, // Remove a callback from the list //队列中的函数调用主要使用了数组的一个方法splice remove: function() { if ( list ) { jQuery.each( arguments, function( _, arg ) { var index; while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 ); // Handle firing indexes 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 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 ]; 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; };

参考:$.Callbacks 源码解析

用jQuery.Callbacks 实现pub/sub 模式

var topics = {};
jQuery.Topic = function( id ) {
  var callbacks, method,
    topic = id && topics[ id ];
  if ( !topic ) {
    callbacks = jQuery.Callbacks();
    topic = {
      publish: callbacks.fire,
      subscribe: callbacks.add,
      unsubscribe: callbacks.remove
    };
    if ( id ) {
      topics[ id ] = topic;
    }
  }
  return topic;
};

// Subscribers
$.Topic( "mailArrived" ).subscribe( fn1 );
$.Topic( "mailArrived" ).subscribe( fn2 );
$.Topic( "mailSent" ).subscribe( fn1 );
// Publisher
$.Topic( "mailArrived" ).publish( "hello world!" );
$.Topic( "mailSent" ).publish( "woo! mail!" );