代码改变世界

jQuery Callbacks

2013-01-19 22:13  Justany_WhiteSnow  阅读(2866)  评论(0编辑  收藏  举报

jQuery.Callbacks是jQuery的多用途核心组件,专职负责回调函数列队管理,其在jQuery的$.ajax() 和 $.Deferred()提供了一些基础功能。

其主要提供了易于管理的批量回调函数处理的功能。

说到批处理,在Javascript库中屡见不鲜:

等等…………

为了理解Callbacks函数的实现,我们先来了解下jQuery.each()。

 

each()

我们可以在jQuery的源文件core.js找到其完整实现:

/*********************
 *    obj: 队列列表
 *    callback: 回调函数
 *    args: 回调函数的参数
 */
jQuery.each = function( obj, callback, args ) {
    var value,
        i = 0,
        length = obj.length,
        isArray = isArraylike( obj );    //obj是否是类Array对象

    //如果有参数
    if ( args ) {
        //如果obj是类Array对象
        if ( isArray ) {
            for ( ; i < length; i++ ) {
                value = callback.apply( obj[ i ], args );
                if ( value === false ) {
                    break;
                }
            }
        //否则
        } else {
            for ( i in obj ) {
                value = callback.apply( obj[ i ], args );
                if ( value === false ) {
                    break;
                }
            }
        }
    //如果没有参数,则是一个更为常用的each函数
    } else {
        if ( isArray ) {
            for ( ; i < length; i++ ) {
                value = callback.call( obj[ i ], i, obj[ i ] );
                if ( value === false ) {
                    break;
                }
            }
        } else {
            for ( i in obj ) {
                value = callback.call( obj[ i ], i, obj[ i ] );
                if ( value === false ) {
                    break;
                }
            }
        }
    }

    return obj;
}

借助这个函数我们jQuery实现了其他each函数。如:

$( "li" ).each(function( index ) {
  console.log( index + ": "" + $(this).text() );
});

这里的each实际上只是一个没有args回调函数参数的jQuery.each。

 

简易Callbacks

实际上对简单的回调函数进行变形,我们也能弄成类似回调函数队列的效果:

function dosomething(__callbacks){
    //do something......
    for(var i = __callbacks.length; i--;){
        __callbacks[i]();
    }
}

但jQuery.CallBacks可以为我们提供更丰富的功能,和更方便的管理方法。

 

jQuery.Callbacks(flags)

flags:一个用空格标记分隔的标志可选列表,用来改变回调列表中的行为

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

回调列表中添加一个回调或回调的集合。

callbacks:一个函数,或者一个函数数组用来添加到回调列表。

  • callbacks.remove(callbacks)

删除回调或回调回调列表的集合。

callbacks:一个函数或函数数组,是从回调列表中删除。 

  • callbacks.fire(arguments)

调用所有回调函数,并将arguments传给他们。

arguments:这个参数或参数列表传回给回调列表。

  • callbacks.disable()

禁用回调列表中的回调。

例子:

function fn1( value ){
    console.log( value );
    return false;
}

function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}
    
var callbacks = $.Callbacks( "unique memory" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn1 ); //重复添加
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.add( fn2 );
callbacks.fire( "baz" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );

/*
output:
foo
fn2 says:foo
bar
fn2 says:bar
baz
fn2 says:baz
foobar
*/

 

optionsCache

我们看到上面的例子中,flags参数是以字符串形式,每个参数以空格间隔,如:

$.Callbacks( "unique memory" );

大家会如何将字符串转成参数呢?

在这里Callbacks会通过正则表达式将字符串转数组,然后再组装成参数对象,如上面的例子,则最后参数对象是:

{
    unique: true,
    memory: true
}

再将这个对象缓存到optionsCache,以便下次使用。

//参数对象缓存
var optionsCache = {};

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

jQuery.Callbacks = function( options ) {
    
    //通过字符串在optionsCache寻找有没有相应缓存,如果没有则创建一个,有则引用
    //如果是对象则通过jQuery.extend深复制后赋给options。
    options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );

    //………………
    
}

其他地方的实现比较像一个单一事件的自定义事件处理对象,通过add添加事件处理函数,用remove删除事件处理函数,用fire触发事件。

有兴趣请参照下面的完整备注。 

 

完整备注

jQuery.Callbacks = function( options ) {

    //通过字符串在optionsCache寻找有没有相应缓存,如果没有则创建一个,有则引用
    //如果是对象则通过jQuery.extend深复制后赋给options。
    options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );

    var // 最后一次触发回调时传的参数
        memory,
        // 列表中的函数是否已经回调至少一次
        fired,
        // 列表中的函数是否正在回调中
        firing,
        // 回调的起点
        firingStart,
        // 回调时的循环结尾
        firingLength,
        // 当前正在回调的函数索引
        firingIndex,
        // 回调函数列表
        list = [],
        // 可重复的回调函数堆栈,用于控制触发回调时的参数列表
        stack = !options.once && [],
        // 触发回调函数列表
        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++ ) {
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                    memory = false; // 阻止未来可能由于add所产生的回调
                    break;    //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环
                }
            }
            //标记回调结束
            firing = false;
            //如果列表存在
            if ( list ) {
                //如果堆栈存在
                if ( stack ) {
                    //如果堆栈不为空
                    if ( stack.length ) {
                        //从堆栈头部取出,递归fire。
                        fire( stack.shift() );
                    }
                //否则,如果有记忆
                } else if ( memory ) {
                    //列表清空
                    list = [];
                //再否则阻止回调列表中的回调
                } else {
                    self.disable();
                }
            }
        },
        // 暴露在外的Callbacks对象
        self = {
            // 回调列表中添加一个回调或回调的集合。
            add: function() {
                if ( list ) {
                    // 首先我们存储当前列表长度
                    var start = list.length;
                    (function add( args ) {
                        //前面我们看到的jQuery.each,对args传进来的列表的每一个对象执行操作
                        jQuery.each( args, function( _, arg ) {
                            //得到arg的类型
                            var type = jQuery.type( arg );
                            //如果是函数
                            if ( type === "function" ) {
                                //确保是否可以重复
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            //如果是类数组或对象
                            } else if ( arg && arg.length && type !== "string" ) {
                                //递归
                                add( arg );
                            }
                        });
                    })( arguments );
                    // 如果正在回调就将回调时的循环结尾变成现有长度
                    if ( firing ) {
                        firingLength = list.length;
                    // 如果有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 jQuery.inArray( fn, list ) > -1;
            },
            // 从列表中删除所有回调函数
            empty: function() {
                list = [];
                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 ) {
                args = args || [];
                args = [ context, args.slice ? args.slice() : args ];
                if ( list && ( !fired || stack ) ) {
                    //如果正在回调
                    if ( firing ) {
                        //将参数推入堆栈,等待当前回调结束再调用
                        stack.push( args );
                    //否则直接调用
                    } else {
                        fire( args );
                    }
                }
                return this;
            },
            // 以给定的参数调用所有回调函数
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },
            // 回调函数列表是否至少被调用一次
            fired: function() {
                return !!fired;
            }
        };

    return self;
};