jQuery.Callbacks:
作用是对加载的回调函数进行统一管理。有once、memory、unique、stopOnFalse四种标志,有fire、add、remove、has、empty、disable、disabled、lock、locked、fireWith、fired这些操作。
需要前置的了解一些知识:
- core_rnotwhite = /\S+/g:正则,用来匹配任何非空白字符(空白字符,包括空格、制表符、换页符等等)。
- var optionsCache = {}:一个空对象,用于存储缓存选项
- createOptions:
function createOptions(options) {
var object = optionsCache[options] = {};
jQuery.each(options.match(core_rnotwhite) || [],
function(_, flag) {
object[flag] = true;
});
return object;
}
传入一个字符串(由空格分隔选项),然后匹配,将字符串分割,然后遍历,作为object的属性,内容为true,返回对象。结果如下:
代码详解:
大体框架:
jQuery.Callbacks = function( options ) {
options = ......
var
memory,
fired,
firing,
firingStart,
firingLength,
firingIndex,
list = [],
stack = !options.once && [],
fire = function(data){
......
}
self = {
add: function() {
.....
},
remove: function() {
.....
},
has: function( fn ) {
.....
},
empty: function() {
.....
},
disable: function() {
.....
},
disabled: function() {
.....
},
lock: function() {
.....
},
locked: function() {
.....
},
fireWith: function( context, args ) {
......
},
fire: function() {
......
},
fired: function() {
......
}
}
return self;
}
可以了解到,函数返回的是self这个对象,通过这个对象的属性方法对内部数据进行操作。按代码长度来看,比较关键的是fire、add。
以下是简单的功能演示:能够执行加载的函数。
下面的函数解析,不是根据代码顺序解析的。
options:
根据传入的参数,将他赋值给内部的options,传入的options的作用是决定回调函数的执行方式,类似于配置。
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );
如果是字符串的话,如果optionsCache[options]以及存在就直接返回optionsCache[options](说明是单个配置项),反之就调用createOptions对options进行解析,返回结果。如果是对象的话,就先赋值给一个空对象,在复制给options。
(看注释的话,就可以知道,options有once、memory、unique、stopOnFalse四类)
以下是简单的功能演示:各个标志的作用:
once:只执行一次fire();
memory:之后加载的也能执行
unique:不会重复触发同一个函数
stopOnFalse:return false之后停止执行
memory、fired、firing、firingStart、firingLength、firingIndex、list、stack
- memory:memory标识
- fired:标识,判断是否list中的函数已经执行过了。
- firing:标识,判断是否有list中的函数正在执行。
- firingStart:第一个被执行的函数在list列表中下标。
- firingLength:标识,正在执行时,list列表中函数的数量。
- firingIndex:正在执行的函数,在list中的下标。
- list:用来存储函数的列表。
- stack:用于暂时存储执行时再次执行函数元素。
has:
has: function( fn ) {
return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
},
输入的参数,如果参数存在的话,使用jQuery.inArray,判断该函数是否存在于list之中,返回true或者false,如果参数不存在的话,返回list是否存在函数的判断结果。
add:
add: function() {
if ( list ) {
var start = list.length;
(function add( args ) {
jQuery.each( args, function( _, arg ) {
var type = jQuery.type( arg );
if ( type === "function" ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && type !== "string" ) {
add( arg );
}
});
})( arguments );
if ( firing ) {
firingLength = list.length;
} else if ( memory ) {
firingStart = start;
fire( memory );
}
}
return this;
},
声明一个start变量为列表的长度,然后一个匿名函数自执行,将add的参数数组作为参数传入匿名函数,然后使用jQuery.each遍历执行。如果元素类型是函数的话,就直接push压入list中(如果opions对象中,unique 为true且以及list中已经存在的话,就不压入list中了);如果参数存在,类型不是string且具有长度的话,就递归继续add。
如果此时list函数正在执行的话,将正在执行的长度加一(新添加的函数也加入到执行的列表中)。
如果memory为true的话,将开始执行函数的list下标,改为start,(即list.length,新加入的着个函数)执行fire。
(其实从这里可以看出,输入memory参数的话,相当于是fire()之后的add()自己执行fire())如下:
最后的hello打印,是add()执行的,而不是fire();因为hello并没有随着fire()执行两次。
remove:
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;
},
根据输入的参数,使用jQuery.each遍历参数数组,移除list中存在的函数元素。如果正在fire执行的话,删除的元素所在的下标小于等于firingIndex的话,firingIndex--,删除的元素所在的下标小于等于firingLength的话,firingLength--。防止数组的改变对正在执行函数的影响。
disable:
disable: function() {
list = stack = memory = undefined;
return this;
},
将list、stack、memory 全部设置为undefined,即之后只调用fire()或者add()的话,就不会执行了fire()了。
self.fireWith、self.fire、fire:
fire = function( data ) {
memory = options.memory && data;
fired = true;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true;
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if ( list ) {
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) {
list = [];
} else {
self.disable();
}
}
},
self = {
fireWith: function( context, args ) {
if ( list && ( !fired || stack ) ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
if ( firing ) {
stack.push( args );
} else {
fire( args );
}
}
return this;
},
fire: function() {
self.fireWith( this, arguments );
return this;
},
}
fire函数,实质上是在调用self.fireWith,将this(self)、fire的参数数组传入self.fireWith;
self.fireWith,先判断,如果列表存在,且第一次执行(!fired),或者stack 存在(stack 执行list的函数中时,调用fire()才会被添加)。如果args参数存在就去参数本身,不存在就取空数组,然后加上this。如果正在执行,就把参数压入stack(stack唯一一个增加元素的地方),如果不是正在执行的话,执行fire(args)。
fire函数,分两种方式,一种是执行list内的函数,从未开始到开始,即这一段代码:代表着firing = true;正在运行list中的函数。运行结束之后,firing = false
另一种就是完毕之后,处理于memory、once标志相关、stack正在执行相关的问题等,在执行中再次的执行(将fire()嵌套在被执行的函数中)。如果stack存在的话就执行fire,同时将函数元素移除栈,表示已经执行过了。stack只有设置为once是,才会使false,([]也是通过if判断的)。如果是memory,就list设置为空,表示只执行一次,如果
stack的嵌套处理如下:(加allow是为了防止循环)
empty:
disable: function() {
list = [];
firingLength = 0;
return this;
},
清空数组,执行长度设置为0。
lock:
lock: function() {
stack = undefined;
if ( !memory ) {
self.disable();
}
return this;
}
将list的内容锁定,如果memory存在的话,则执行disable。
locked:
locked: function() {
return !stack;
},
返回stack状态,判断是否被锁住(once或者执行了disable),
fired:
fired: function() {
return !!fired;
}
是否是执行过list内部的数组。
disabled:
disabled: function() {
return !list;
},
返回,list状态的取反。作为disabled的状态标识。
小结:
简单的来说,jQuery.Callbacks就是围绕着存储函数的数组list,现实最简单增加和减少元素,然后是根据四个标识实现不同的执行,慢慢扩充。
这里对jQuery.Callbacks并不是一行行完全解析下来的,只讲了大致功能,解释的有点粗糙,详细的解析在后面。
jQuery.Callbacks虽然代码不多,但其中对函数周期流程的处理思想非常棒,对思维的要求较高,很值得学习。
源码:2880--------3042行
jQuery.Callbacks = function(options) {
// Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
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;
fired = true;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true;
for (; list && firingIndex < firingLength; firingIndex++) {
if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) {
memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if (list) {
if (stack) {
if (stack.length) {
fire(stack.shift());
}
} else if (memory) {
list = [];
} else {
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; (function add(args) {
jQuery.each(args,
function(_, arg) {
var type = jQuery.type(arg);
if (type === "function") {
if (!options.unique || !self.has(arg)) {
list.push(arg);
}
} else if (arg && arg.length && type !== "string") {
// Inspect recursively
add(arg);
}
});
})(arguments);
// Do we need to add the callbacks to the
// current firing batch?
if (firing) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
} else if (memory) {
firingStart = 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;
},
// Check if a given callback is in the list.
// If no argument is given, return whether or not list has callbacks attached.
has: function(fn) {
return fn ? jQuery.inArray(fn, list) > -1 : !!(list && list.length);
},
// Remove all callbacks from the list
empty: function() {
list = [];
firingLength = 0;
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) {
if (list && (!fired || stack)) {
args = args || [];
args = [context, args.slice ? args.slice() : args];
if (firing) {
stack.push(args);
} else {
fire(args);
}
}
return this;
},
// Call all the callbacks with the given arguments
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;
};