

jQuery的ajax、deferred通过回调实现异步,其实现核心是Callbacks。
使用首先要先新建一个实例对象。创建时可以传入参数flags,表示对回调对象的限制,可选值如下表示。
stopOnFalse:回调函数队列中的函数返回false时停止触发
once:回调函数队列只能被触发一次
memory:记录上一次触发队列传入的值,新添加到队列中的函数使用记录值作为参数,并立即执行。
unique:函数队列中函数都是唯一的
var cb = $.Callbacks('memory');
cb.add(function(val){
console.log('1: ' + val)
})
cb.fire('callback')
cb.add(function(val){
console.log('2: ' + val)
})
// consoleCallbacks 提供了一系列实例方法来操作队列和查看回调对象的状态。
add: 添加函数到回调队列中,可以是函数或者函数数组
remove: 从回调队列中删除指定函数
has: 判断回调队列里是否存在某个函数
empty: 清空回调队列
disable: 禁止添加函数和触发队列,清空回调队列和上一个传入的值
disabled: 判断回调对象是否被禁用
lock: 禁用fire,若memory非空则同时add无效
locked: 判断是否调用了lock
fireWith: 传入context和参数,触发队列
fire: 传入参数触发对象,context是回调对象
$.Callback()方法内部定义了多个局部变量和方法,用于记录回调对象的状态和函数队列等,返回self,在self实现了上述回调对象的方法,用户只能通过self提供的方法来更改回调对象。这样的好处是保证除了self之外,没有其他修改回调对象的状态和队列的途径。
其中,firingIndex为当前触发函数在队列中的索引,list是回调函数队列,memory记录上次触发的参数,当回调对象实例化时传入memory时会用到,queue保存各个callback执行时的context和传入的参数。self.fire(args)实际是self.fireWith(this,args),self.fireWith内部则调用了在Callbacks定义的局部函数fire。
...
// 以下变量和函数 外部无法修改,只能通过self暴露的方法去修改和访问
var // Flag to know if list is currently firing
firing,
// Last fire value for non-forgettable lists
// 保存上一次触发callback的参数,调用add之后并用该参数触发
memory,
// Flag to know if list was already fired
fired,
// Flag to prevent firing
// locked==true fire无效 若memory非空则同时add无效
locked,
// Actual callback list
// callback函数数组
list = [],
// Queue of execution data for repeatable lists
// 保存各个callback执行时的context和传入的参数
queue = [],
// Index of currently firing callback (modified by add/remove as needed)
// 当前正触发callback的索引
firingIndex = -1,
// Fire callbacks
fire = function() {
...
},
// Actual Callbacks object
self = {
// Add a callback or a collection of callbacks to the list
add: function() {
...
},
...
// Call all callbacks with the given context and arguments
fireWith: function( context, args ) {
if ( !locked ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ]; // :前为args是数组,:后是string
queue.push( args );
if ( !firing ) {
fire();
}
}
return this;
},
// Call all the callbacks with the given arguments
fire: function() {
self.fireWith( this, arguments );
return this;
},
...
}通过self.add添加函数到回调队列中,代码如下。先判断是否memory且非正在触发,如果是则将fireIndex移动至回调队列的末尾,并保存memory。接着使用立即执行函数表达式实现add函数,在该函数内遍历传入的参数,进行类型判断后决定是否添加到队列中,如果回调对象有unique标志,则还要判断该函数在队列中是否已存在。如果回调对象有memory标志,添加完毕之后还会触发fire,执行新添加的函数。
add: function() {
if ( list ) {
// If we have memory from a past run, we should fire after adding
// 如果memory非空且非正在触发,在queue中保存memory的值,说明add后要执行fire
// 将firingIndex移至list末尾 下一次fire从新add进来的函数开始
if ( memory && !firing ) {
firingIndex = list.length - 1;
queue.push( memory );
}
( function add( args ) {
jQuery.each( args, function( _, arg ) {
// 传参方式为add(fn)或add(fn1,fn2)
if ( jQuery.isFunction( arg ) ) {
/**
* options.unique==false
* 或
* options.unique==true&&self中没有arg
*/
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
// 传参方式为add([fn...]) 递归
// Inspect recursively
add( arg );
}
} );
} )( arguments ); //arguments为参数数组 所以add的第一步是each遍历
//添加到list后若memory真则fire,此时firingIndex为回调队列的最后一个函数
if ( memory && !firing ) {
fire();
}
}
return this;
}fire、fireWith方法内部实际调用了局部函数fire,其代码如下。触发时,需要更新fired和firing,表示已触发和正在触发。通过for循环执行队里中的函数。结束循环后,将firingIndex更新为-1,表示下次触发从队列中的第一个函数开始。遍历在fireWith中更新过的queue,queue是保存数组的数组,每个数组的第一个元素是context,第二个元素是参数数组。执行函数时要是否返回false且回调对象有stopOnFalse标志,如果是则停止触发。
// Fire callbacks
fire = function() {
// Enforce single-firing
// 执行单次触发
locked = locked || options.once;
// Execute callbacks for all pending executions,
// respecting firingIndex overrides and runtime changes
// 标记已触发和正在触发
fired = firing = true;
// 循环调用list中的回调函数
// 循环结束之后 firingIndex赋-1 下一次fire从list的第一个开始 除非firingIndex被修改过
// 若设置了memory,add的时候会修改firingIndex并调用fire
// queue在fireWith函数内被更新,保存了触发函数的context和参数
for ( ; queue.length; firingIndex = -1 ) {
memory = queue.shift();
while ( ++firingIndex < list.length ) {
// Run callback and check for early termination
// memory[0]是content memory[1]是参数
if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
options.stopOnFalse ) {
// Jump to end and forget the data so .add doesn't re-fire
// 当前执行函数范围false且options.stopOnFalse==true 直接跳至list尾 终止循环
firingIndex = list.length;
memory = false;
}
}
}
// 没设置memory时不保留参数
// 设置了memory时 参数仍保留在其中
// Forget the data if we're done with it
if ( !options.memory ) {
memory = false;
}
firing = false;
// Clean up if we're done firing for good
if ( locked ) {
// Keep an empty list if we have data for future add calls
if ( memory ) {
list = [];
// Otherwise, this object is spent
} else {
list = "";
}
}
},