本来想先分析defer的,但是在defer的文件中,发现它依赖于另外一个比较重要的模块callbacks。于是,这节就先分析callbacks吧。
在js开发中,由于没有多线程,经常会遇到回调这个概念,比如说,在 ready 函数中注册回调函数,注册元素的事件处理等等。在比较复杂的场景下,当一个事件发生的时候,可能需要同时执行多个回调方法,可以直接考虑到的实现就是实现一个队列,将所有事件触发时需要回调的函数都加入到这个队列中保存起来,当事件触发的时候,从这个队列中依次取出保存的函数并执行。
callbacks指一个多用途的回调列表对象,提供了强大的的方式来管理回调函数列表。简单的使用方式如下:
function fn1( value ){
console.log( value );
}
function fn2( value ){
fn1("fn2 says:" + value);
return false;
}
var callbacks = $.Callbacks();
callbacks.add( fn1 );
callbacks.fire( "foo!" ); // outputs: foo!
callbacks.add( fn2 );
callbacks.fire( "bar!" ); // outputs: bar!, fn2 says: bar!
简单来说就是管理回调函数的执行的。这个模块主要还是提供给jquery内部的ajax和defer使用。
$.callbacks(flags)中的flags是$.Callbacks()的一个可选参数, 结构为一个用空格标记分隔的标志可选列表,用来改变回调列表中的行为 (比如. $.Callbacks( ‘unique stopOnFalse’ ))。
可用的 flags:
源码如下:
jQuery.Callbacks = function( options ) {
options = typeof options === "string" ?
createOptions( options ) :
jQuery.extend( {}, options );
var firing,
memory,
fired,
locked,
list = [],
queue = [],
firingIndex = -1,
fire = function() {
locked = options.once;
fired = firing = true;
for ( ; queue.length; firingIndex = -1 ) {
memory = queue.shift();
while ( ++firingIndex < list.length ) {
if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
options.stopOnFalse ) {
firingIndex = list.length;
memory = false;
}
}
}
if ( !options.memory ) {
memory = false;
}
firing = false;
if ( locked ) {
if ( memory ) {
list = [];
} else {
list = "";
}
}
},
self = {
add: function() {
if ( list ) {
if ( memory && !firing ) {
firingIndex = list.length - 1;
queue.push( memory );
}
( function add( args ) {
jQuery.each( args, function( _, arg ) {
if ( jQuery.isFunction( arg ) ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
// Inspect recursively
add( arg );
}
} );
} )( arguments );
if ( memory && !firing ) {
fire();
}
}
return this;
},
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;
},
has: function( fn ) {
return fn ?
jQuery.inArray( fn, list ) > -1 :
list.length > 0;
},
empty: function() {
if ( list ) {
list = [];
}
return this;
},
disable: function() {
locked = queue = [];
list = memory = "";
return this;
},
disabled: function() {
return !list;
},
lock: function() {
locked = queue = [];
if ( !memory && !firing ) {
list = memory = "";
}
return this;
},
locked: function() {
return !!locked;
},
fireWith: function( context, args ) {
if ( !locked ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
queue.push( args );
if ( !firing ) {
fire();
}
}
return this;
},
fire: function() {
self.fireWith( this, arguments );
return this;
},
fired: function() {
return !!fired;
}
};
return self;
};
下面,我们就直接从构造方法和返回self中提供的操作方法,按照上面的操作方法列表顺序依次分析。
取出了options(flag)并保存,并定义了若干参数,分别是:
还有一个fire内部方法,这个比较重要,它的执行过程如下:
首先,locked = options.once;也就是说,如果flag中有once选项,那么只要执行过fire,下一次就会锁住。
接着,fired = firing = true;标示正在执行fire。
之后,循环从queue中取出执行执行环境,赋值给memory(保存了当前执行环境)。并不断顺序执行列表中的回调函数。期间还判断了stopOnFalse选项。当回调返回false时,并且有stopOnFalse选项,就会销毁memory,相当于memory选项没有了。
再接着,判断options.memory是否定义,如果没有,同样销毁memory(上一次的执行环境)。
最后,判断锁定,如果锁定,外部fire就不用了,由是否有递延指定add(会调用内部fire)是否可用,无递延就要disable掉(locked+list)。
最后,返回self,self中暴露了各种操作方法,如下。
首先,判断list(回调函数列表),实现了once功能。在上述fire函数中,如果有once标志,list将被赋值成空。
接着,如果有memory(记录着上次触发时使用的参数),并且不在执行的话,保存firingIndex(回调列表list的触发索引,也会用在指定add递延触发位置),并且queue(多次fire调用(因为可能被嵌套调用)的调用参数列表)存入memory。
接着,把add的回调函数,压入list,这里支持递归add。
最后,如果不在firing中,并且是memory模式,则执行fire,由于之前queue中压入了memory,并记录了firingIndex,所以fire会执行刚刚被add进list的回调函数。如果在firing中,当前的函数,自然也会被执行。
这个比较简单,把locked ,queue,list,memory都清空,add将无法使用,fire同样不行。
直接返回!list。
清空list 。
直接调用了fireWith,并传入了参数。
返回!!fired(双感叹号指强制转换成布尔值)。
判断是否locked,如果不是,把参数格式化成[环境,参数数组],写进queue。并执行fire。
判断是否有指定回调,无参数则判断回调列表是否空。
无递延(每次执行完memory重置为false)或没触发过,则直接禁用;否则还是可以相应add后的递延函数。
返回!!locked。
移除回调,支持多参数。去掉所有相同回调,当回调内调用remove时,若删除项为已执行项,修正了firingIndex位置。