/ javaScript

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 源码加注释