Callbacks是JQ的一个回调对象,可以用来添加回调,执行回调,删除回调等等。并提供一些参数如once,memory,unique等来进行特殊需求的控制。这里就不举例说明Callbacks的用法了。具体详细说明可以参见:http://api.jquery.com/jQuery.Callbacks/
我们学习源码,需先了解如何使用,这里假设我们已经知道如何使用Callbacks了。
他的实现思路就是: 构建一个存放回调的数组,如var list = [],通过闭包使这条回调数组保持存在。添加回调时,将回调push进list,执行则遍历list执行回调。
看思路貌似很简单,我们就直接来看源码吧,对于源码的阅读,我的习惯是大概过一次源码,看看注释了解下大概是作何功用,再直接运行最简单的示例,跟踪下它的执行流程,这个过程中各变量的变化,返回值等等。
这里,在过源码的时候,我们就知道当调用了var cal = $.Callbacks()的时候,返回来的是一个对象(self),这个对象里面实现了add,remove,fire等方法,也就是说,可以用cal.add(fn)来添加回调,cal.remove()来删除回调。
好吧,直接上代码解释,只解释了add和fire两个重要方法,其他都较简单。
// String to Object options format cache
var optionsCache = {};
// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.split( core_rspace ), function( _, flag ) {
object[ flag ] = true;
});
return object;
}
/*
* 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: will ensure the callback list can only be fired once (like a Deferred)
*
* memory: 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: will ensure a callback can only be added once (no duplicate in the list)
*
* stopOnFalse: interrupt callings when a callback returns false
*
*/
jQuery.Callbacks = function( options ) {
// Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
// options 这里设计为一个对象,这一步就是先判断下options缓存里有没这个对象存在,有直接拿来用,没有则通过createOptions方法将传过来的字符串转化为一个对象,对象形式如下:
// options = { 'once': true,'memory': true}
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );
var // Last fire value (for non-forgettable lists)
memory,
// Flag to know if list was already fired
fired,
// Flag to know if list is currently firing
firing,
// First callback to fire (used internally by add and fireWith)
firingStart,
// End of the loop when firing
firingLength,
// Index of currently firing callback (modified by remove if needed)
firingIndex,
// Actual callback list
list = [],
// Stack of fire calls for repeatable lists
stack = !options.once && [],
// Fire callbacks
fire = function( data ) {
memory = options.memory && data; //当$.Callbacks('memory')时,保存data,data为数组[context,args]
fired = true; //表示已经执行,用于表示队列里的回调已经执行过一次
firingIndex = firingStart || 0; //执行队列的下标,相当于普通循环的i,当$.Callbacks('memory')时,需设置firingIndex
firingStart = 0; //重置队列起始值
firingLength = list.length; //保存队列的长度
firing = true; //标示正在执行中
for ( ; list && firingIndex < firingLength; firingIndex++ ) { //
//遍历回调数组,执行每一个回调,当回调返回false且有传入stopOnFalse时,也就是$.Callbacks('stopOnFalse'),中止后面回调的执行. PS:这个循环每次都判断list,这个没有必要,可以提到外面判断一次即可
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
memory = false; // To prevent further calls using add //当$.Callbacks('memory stopOnFalse')时,memory的作用将失效
break;
}
}
firing = false; //函数执行完后,将执行中的标示设为false
if ( list ) {
if ( stack ) { //执行完回调后,看一下stack是否有回调,有拿出来执行
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) { //如果没有stack,证明传了once,这里的Callbacks会是这样:$.Callbacks('once memory')
list = [];
} else { //当是$.Callbacks('once')的时候
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; //添加回调函数之前,先保存当前回调函数列表的长度,主要用于当Callbacks传入memory参数时
//这里用了一个立即执行的add函数来添加回调
//直接遍历传过来的arguments进行push
(function add( args ) {
jQuery.each( args, function( _, arg ) {
var type = jQuery.type( arg );
//如果所传参数为函数,则push
if ( type === "function" ) {
if ( !options.unique || !self.has( arg ) ) { //当$.Callbacks('unique')时,保证列表里面不会出现重复的回调
list.push( arg );
}
} else if ( arg && arg.length && type !== "string" ) { //假如传过来的参数为数组或array-like,则继续调用添加,从这里可以看出add的传参可以有add(fn),add([fn1,fn2]),add(fn1,fn2)
// Inspect recursively
add( arg );
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch?
// 这里是我不解的地方,firing是标识回调数组正在执行中,也就是fire正在执行,那这里就重置回调数组的长度,但我不知道什么样的代码下这里会执行到
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
// 在fire方法里,只有options.memory为真时,memory才有值,所以,这里的memory保存的是上一次fire时的memory值,而这个的作用就是要立即执行新添加的回调,让新添加的回调也能输出之前fire时传的值。
//这里也是$.Callbacks('memory')这个参数作用的地方,有了这个参数,每次add也会执行一次memory
} else if ( memory ) {
firingStart = start; //上面保存的start值产生作用的地方
fire( memory );
}
}
return this;
},
// Remove a callback from the list
remove: function() {
if ( list ) {
jQuery.each( arguments, function( _, arg ) {
var index;
while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );
// Handle firing indexes
if ( firing ) {
if ( index <= firingLength ) {
firingLength--;
}
if ( index <= firingIndex ) {
firingIndex--;
}
}
}
});
}
return this;
},
// Control if a given callback is in the list
has: function( fn ) {
return jQuery.inArray( fn, list ) > -1;
},
// Remove all callbacks from the list
empty: function() {
list = [];
return this;
},
// Have the list do nothing anymore
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 ]; //slice只用于字符串或数组,这里为什么要判断一下是不是字符串或数组呢,不明
if ( list && ( !fired || stack ) ) { //fired表示已经执行过,如果已经执行过了,就要看stack了,他为真才会继续执行
if ( firing ) { //firing表示执行中,如果是在执行中,则将其推入stack,stack在这里相当于一个缓存数组,用于当fire忙时暂存下回调,但我也是整不出一段代码让这里执行到,暂时不明白
stack.push( args );
} else {
fire( args ); //执行回调
}
}
return this;
},
// Call all the callbacks with the given arguments
// 直接调用fire,回调执行的上下文是self,而上面的fireWidth则可以通过传入context改变回调的执行上下文
fire: function() {
self.fireWith( this, arguments );
return this;
},
// To know if the callbacks have already been called at least once
fired: function() {
return !!fired;
}
};
return self;
};
此文所讲jQuery版本为1.8.3 : http://code.jquery.com/jquery.js