jQuery.Callbacks源码解读

序言:

最近在学习jQuery.Callbacks对象,看jQuery.Callbacks的API文档,不是很懂,因此看看其源码部分,理解其使用方法,记录下自己在阅读源码时的记录并分享给大家。

有理解不透的地方望同仁指点,代码来源:jQuery 1.9.1版本。作者:华子yjh,欢迎转载,转载时请注明出处并附上原文链接

 

一、源码解读

/*
 * 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:          确保回调列表仅只fire一次
                     will ensure the callback list can only be fired once (like a Deferred)
 *
 *    memory:        在执行过fire后,保存之前fire时的参数,该参数会传递给在add中执行的最新添加的回调
                     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:        确保在add操作中,阻止存在回调列表中的回调再次被添加到回调列表中
                     will ensure a callback can only be added once (no duplicate in the list)
 *
 *    stopOnFalse:   当正在执行的回调返回false,将中断其他未执行回调的执行
                     interrupt callings when a callback returns false
 *
 */

var optionsCache = {},
    // Used for splitting on whitespace
    core_rnotwhite = /\S+/g;

// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
    // optionsCache[ options ] 用于缓存 object所引用的值
    var object = optionsCache[ options ] = {};
    jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
        object[ flag ] = true;
    });
    return object;
}

jQuery.Callbacks = function( options ) {

    // Convert options from String-formatted to Object-formatted if needed
    // (we check in cache first)
    options = typeof options === "string" ?
        // 只有当执行$.Callbacks(参数相同)二次及以上时,才不会执行createOptions函数
        ( optionsCache[ options ] || createOptions( options ) ) :
        // 说明也可以这样$.Callbacks({once:true, memory:true})使用
        jQuery.extend( {}, options );

    var // Flag to know if list is currently firing
        firing,
        // Last fire value (for non-forgettable lists)
        memory,
        // Flag to know if list was already fired
        fired,
        // End of the loop when firing
        firingLength,
        // Index of currently firing callback (modified by remove if needed)
        firingIndex,
        // First callback to fire (used internally by add and fireWith)
        firingStart,
        // Actual callback list
        list = [],
        // Stack of fire calls for repeatable lists
        stack = !options.once && [],
        // Fire callbacks
        // data传递的是一个数组
        // 使用Callbacks.fireWidth时,data包含fireWith函数传递的一个上下文环境和一个数组
        // 使用Callbacks.fire时,data包含Callbacks对象和fire函数的arguments对象
        fire = function( data ) {
            // 如果options.memory为true,记录下data传递的数组
            memory = options.memory && data;
            fired = true;
            // 如果options.memory为true,firingStart为上一次Callbacks.add后回调列表的length值
            firingIndex = firingStart || 0;
            // 重置firingStart为0
            firingStart = 0;
            firingLength = list.length;
            firing = true;
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                // 将data作为参数,执行回调列表中的所有回调
                // 如果回调列表中其中一个回调返回false,且options.stopOnFalse为true,则中断接下来其他回调的执行
                // 如果options.memory为true,将memory设置为false,阻止在Callbacks.add中新增回调的执行
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                    memory = false; // To prevent further calls using add
                    break;
                }
            }
            firing = false;
            if ( list ) {
                // 如果options.once为false
                if ( stack ) {
                    // stack在fireWith操作时可能拥有成员
                    // 当stack拥有成员时(只有一个),并将其成员作为参数传递给将要执行的所有回调
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                }
                // 如果options.memory为true且options.once为true,那么在add中执行一次fire(只执行最新添加的回调)
                // 这里设置list = [],那么再次fire时,将不会有任何操作
                else if ( memory ) {
                    list = [];
                }
                // 如果存在options.once为true,options.memory为false的其他情况,将禁用Callbacks对象
                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 );
                            // 如果arg是一个函数
                            if ( type === "function" ) {
                                // arg为要新增到回调列表中的回调
                                // 如果arg不存在回调列表中,则将它增加到回调列表中
                                // 如果arg存在回调列表中,且options.unique为false,则将它增加到回调列表中
                                // 如果arg存在回调列表中,且options.unique为true,则不执行push动作
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            }
                            // 如果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?
                    // 如果回调列表中的回调正在执行时,其中的一个回调函数执行了Callbacks.add操作
                    // 上句话可以简称:如果在执行Callbacks.add操作的状态为firing时
                    // 那么需要更新firingLength值
                    if ( firing ) {
                        firingLength = list.length;
                    // With memory, if we're not firing then
                    // we should call right away
                    // 如果options.memory为true,则将memory做为参数,应用最近增加的回调函数
                    } else if ( memory ) {
                        firingStart = start;
                        fire( memory );
                    }
                }
                return this;
            },
            // Remove a callback from the list
            remove: function() {
                if ( list ) {
                    jQuery.each( arguments, function( _, arg ) {
                        var index;
                        // 通过找到arguments成员在回调列表中索引位置遍历arguments对象,并将arguments成员从回调列表中移除
                        while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                            list.splice( index, 1 );
                            // Handle firing indexes
                            // 如果在执行Callbacks.remove操作的状态为firing时
                            // 则更新firingLength和firingIndex的值
                            if ( firing ) {
                                if ( index <= firingLength ) {
                                    firingLength--;
                                }
                                // 特殊处理,如果移除的回调的索引小于当前正在执行回调的索引,则firingIdex--
                                // 后面未执行的回调则得以正常执行
                                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.
            // 检查fn是否存在回调列表中
            has: function( fn ) {
                return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
            },
            // Remove all callbacks from the list
            // 清空回调列表中的所有回调
            empty: function() {
                list = [];
                return this;
            },
            // Have the list do nothing anymore
            // 禁用Callbacks对象
            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 ) {
                args = args || [];
                args = [ context, args.slice ? args.slice() : args ];
                if ( list && ( !fired || stack ) ) {
                    // 如果fired为true,options.once为false,且如果在执行fireWith操作的状态为firing
                    // 则将处理过的args作为stack数组的第一个数组项,并在firing为false后,将stack第一个数组项作为参数传递给将要执行的所有回调
                    
                    // 如果fired为true,options.once为true,则不执行任何操作
                    // 可以看出,$.Callbacks('once')表示回调对象只fire一次
                    if ( firing ) {
                        stack.push( args );
                    }
                    // 第一次执行fireWith时,一定执行else分支
                    else {
                        fire( args );
                    }
                }
                return this;
            },
            // Call all the callbacks with the given arguments
            // 将Callbacks对象,及Callbacks.fire函数中的arguments对象传递给Callbacks.fireWith函数,并执行
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },
            // To know if the callbacks have already been called at least once
            // 是否执行过fire函数
            fired: function() {
                return !!fired;
            }
        };
    
    //返回Callbacks对象
    return self;
};

 

二、jQuery.Callbacks内部中firing为true的实例

1、在Callbacks.add中firing为true的实例

$(function(){
    // 定义三个将要增加到回调列表的回调函数fn1,fn2,fn3        
    function fn1(arg){
        console.log( 'fn1 says:' + arg );
        // 在fn1中执行Callbacks.add操作,此时Callbacks函数内部的firingLength将会得到更新
        $callbacks.add(fn2);
    }
    function fn2(arg){
        console.log( 'fn2 says:' + arg );
    }
    function fn3(arg){
        console.log( 'fn3 says:' + arg );
    }
    
    // Callbacks传递了memory
    // 也可以这样使用$.Callbacks({ memory: true });
    var $callbacks = $.Callbacks('memory');
    
    // 将fn1增加到回调列表中,因为在fn1中有执行了add(fn2)操作,因此回调列表中的回调为fn1,fn2
    $callbacks.add(fn1);
    
    // output: fn1 says:foo
    // output: fn2 says:foo
    $callbacks.fire('foo');
    
    // 将之前fire的参数传递给最近增加的回调fn3,并执行fn3
    // output: fn3 says:foo
    $callbacks.add(fn3);
    
    // 再执行一次fire,注意此时回调列表中的回调一次是fn1,fn2,fn3,fn2
    // output: fn1 says:baz
    // output: fn2 says:baz
    // output: fn3 says:baz
    // output: fn2 says:baz
    // 如果期望回调列表中只有fn1,fn2,fn3,只需在Callbacks函数中传入unique
    $callbacks.fire('baz');
});

 

2、在Callbacks.fireWith中firing为true的实例

$(function(){    
    function fn1(arg){
        console.log( 'fn1 says:' + arg );
    }
    function fn2(arg){
        console.log( 'fn2 says:' + arg );
        $callbacks.fireWith(window, ['yjh']);
        
        // 一定要执行这一步,否则将会陷入死循环
        $callbacks.remove(fn2);
    }
    
    var $callbacks = $.Callbacks();
    $callbacks.add(fn1);
    
    // output: fn1 says:foo
    $callbacks.fire('foo');    

    $callbacks.add(fn2);    
    // output: fn1 says:baz
    // output: fn2 says:baz
    // output: fn1 says:yjh
    $callbacks.fire('baz');
});

 

posted @ 2013-04-11 13:52  杨君华  阅读(1708)  评论(4编辑  收藏  举报