JQuery那点事 -- Callback

Callback(事件队列)

所谓的事件队列就是一个函数的集合对象,这个对象下有添加、删除、执行函数等等各种操作集合中函数的方法。 顺便说一下 在 JQueryPromise 的实现也与事件队列密不可分,那么接下来从 jQuery 源码来看 Callback 是如何实现、应该如何去使用。

JQuery 中 Callback 的简单实现

以下代码为 jQuery 3.1.0 中,关于 Callbacks 源码中的一部分。经过我的一些修改,仅仅实现了主要的逻辑。

function Callbacks(){  
    var // 函数队列是否被执行的标识
        fired,
        // 函数队列,用于存放 function
        list = [],
        // 当前在函数队列中执行的函数的位置(index)
        firingIndex = -1,
        // 执行函数队列
        fire = function() {
            while (++firingIndex < list.length) {
                list[firingIndex]();
            }
        },
        // 调用 Callbacks 返回的对象
        self = {
            // 在函数队列中添加函数。
            add: function(fun) {
                if (list) {
                    list.push(fun);
                }
                return this;
            },
            // 从函数列表中去除函数
            remove: function(fun) {
                var index;
                // 重复函数判断
                while ((index = jQuery.inArray(fun, list, index)) > -1) {
                    list.splice(index, 1);
                    // 纠正正在执行的函数的位置信息
                    if (index <= firingIndex) {
                        firingIndex--;
                    }
                }
                return this;
            },
            // 移除所有的函数
            empty: function() {
                if (list) {
                    list = [];
                }
                return this;
            },
            // 执行函数队列
            fire: function() {
                fire();
                return this;
            },
        };
    return self;
}

调用 Callbacks() 就能生成一个简易版的事件队列,调用返回对象下的 fire 方法就能依次执行函数队列下的函数。

var callbacks = Callbacks();  
var fun1 = function () {  
    console.log('fun1 start');
};
var fun2 = function () {  
    console.log('fun2 start');
};
callbacks.add(fun1).add(fun2);  
callbacks.fire();

// console
// fun1 start
// fun2 start

简易版 Callbacks 的进一步升级

js 一个极其重要的特点就是可以使用不定数量的参数。所以可以将 add 方法改成下面这样子,更一步接近了 JQuery 中源码的样子:

add: function() {

    // 添加函数,递归添加
    ( function add( args ) {
        jQuery.each( args, function( _, arg ) {
            if ( jQuery.isFunction( arg ) ) {
                list.push( arg );
            } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
                // 对于 [function,function] 参数的添加
                add( arg );
            }
        } );
    } )( arguments );

    return this;
}

接受参数形如 (fun,[fun,fun],fun) 。

callbacks.add(fun1, fun2, [fun3, fun4]);  

同样的 remove 也可修改为:

remove: function() {  
    jQuery.each( arguments, function( _, arg ) {
        var index;
        // 重复函数判断
        while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
            list.splice( index, 1 );

            // Handle firing indexes
            // 纠正正在执行的函数的位置信息
            if ( index <= firingIndex ) {
                firingIndex--;
            }
        }
    } );
    return this;
}

接受参数形如 (fun,fun) 。

callbacks.remove(fun1, fun2);  

功能性方法添加

当然在 jQuery 源码中还有一些功能性的方法。

  • has 判断函数是否在函数队列中
has: function( fn ) {  
    return fn ?
        jQuery.inArray( fn, list ) > -1 :
        list.length > 0;
}
  • empty 移除所有的函数
empty: function() {  
    if ( list ) {
        list = [];
    }
    return this;
}
  • disable && disabled | 无效这个 callbacks && 查看当前 callbacks 的状态
disable: function() {  
    list  = "";
    return this;
},
disabled: function() {  
    return !list;
}

升级 Callbacks 方法

经过上面这几个步骤,就可以大概拼凑出一个接近 JQuery 源码的 Callbacks

但是 jQuery 源码中 Callbacks 拥有更强大的功能,可以拥有 4 中状态:

  • once: 只能被执行一次的函数队列
  • memory: 执行后会保留执行的上下文环境和参数列表的函数队列。
  • unique: 只能添加不同函数的函数队列。
  • stopOnFalse: 只依次执行函数队列中的函数时,只要有函数报错,队列中的函数便不继续运行下去。能添加不同函数的函数队列。

这 4 中 Callbacks 的实现需要在上面我们拼凑出 Callbacks 上加上一些流程控制。具体的请看给出的 jQuery 源码中的注释(点击查看)

注: jQuery 中的 Callbacks 允许两种声明方式:

// 方式1
jQuery.Callbacks( "once memory" )  
// 方式2
jQuery.Callbacks( { once: true, memory: true } )

点击查看 Callbacks 源码加注释