您的位置:首页 > Web前端 > JQuery

jquery源码之低调的回调函数队列--Callbacks

2015-06-12 00:09 633 查看
jQuery中有一个很实用的函数队列,可能我们很少用到,但他在jQuery内部却有着举足轻重的地位。

他就是Callbacks. jQuery作者用它构建了很多非常重要的模块。比如说$.Deferred。

Callbacks 说白了就是个数组,里面存了很多函数对象。然而他真的 just so so么?

好吧,爱因斯坦也只是个人,但他真的仅仅是个普普通通的人吗?Callbacks也不是。

不说废话了,先看源码。

// 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 = 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;
},
// 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 ];
if ( list && ( !fired || stack ) ) {
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;
};


代码只有仅仅200行不到,但真正看起来却又点绕,

《think in java》中有这么一句,理解一个程序最好的方法,就是把它看做一个服务的提供者。

那他提供了那些服务:

首先我们看看返回的self对象

{
// 添加方法
add: function() {},
// 删除
remove: function() {},
// 是否包含
has: function() {},
// 清空
empty: function() {},
// 禁用
disable: function() {},
// 加锁
lock: function() {},
// 是否加锁
locked: function() {},
// 触发
fireWith: function(){},
fire: function() {},
// 是否触发
fired: function() {}
}


  用途都十分清晰,那我们再看看参数,程序是服务的提供者,那么参数作为程序的入口的携带者,一般会用来装配一些属性。

显然这里就是这样。

先看Callbacks内部关于参数部分的代码。

// 官方注释,将配置的options由string格式转换为object格式如果需要的话
// Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
options = typeof options === "string" ?
// 注意这里, 这里去取optionsCache的值,或者调用
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );


  在看看createOptions方法吧,其实就是个转换方法,还带有缓存功能。

// String to Object options format cache
// 建立一个缓存对象
var optionsCache = {};

// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
// 创建optionsCache中的options属性
var object = optionsCache[ options ] = {};
// 这里用到 each方法遍历
// options.split( core_rspace )  根据空格划分为数组
// _在jquery中通常用来作为占位符,即忽略的参数
jQuery.each( options.split( core_rspace ), function( _, flag ) {
// 遍历以后将切割后的每个属性设置为true
object[ flag ] = true;
});
return object;
}
// 可能例子会更清晰,
var obj = createOptions( "once memory");
/*
obj;
{
once: true,
memory: true
}
*/


  接下来就是具体的实现了,jQuery的实现一直是十分巧妙的,当然这可能仅仅是小菜我看来。

/*
* 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 = typeof options === "string" ?
// 注意这里, 这里去取optionsCache的值,或者调用createOptions
// 我们看看createOptions函数
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );

var // Last fire value (for non-forgettable lists)
// 以前触发的值(为了记忆的list,记忆了上次调用时所传递的基本信息(即记忆了参数))
memory,
// 是否触发
// Flag to know if list was already fired
fired,
// 是否正在触发
// Flag to know if list is currently firing
firing,
// 第一个被触发的function
// 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,
// 内部存放function的数组
// Actual callback list
list = [],
// 用来存放重复调用的数组,(当Callbacks被配置了 once属性,则为false)
// Stack of fire calls for repeatable lists
stack = !options.once && [],
// 内部触发函数,这里看到jquery隐藏信息的习惯了
// 作为该模块的核心方法
// 它没有暴露给外部,
// 《代码大全》 有提到信息隐藏的好处。
// Fire callbacks
fire = function( data ) {
// 在设置memory的情况下为 传递过来的参数data, 否则为undefined
memory = options.memory && data;
// 进入到这时标记已触发
fired = true;
// 当前触发索引设置为开始,或者0
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true;
// for循环触发list中的函数
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
// 如果stopOnFalse被设置,则检查调用函数后是否返回false
// 如果返回则终止触发,
// 注意触发参数 为一个多维数组
// data = [
//	context,
//	[args]
//]  这应该是由外部封装成固定格式,再传递过来的参数
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
memory = false; // To prevent further calls using add
break;
}
}
// 设置正在触发为false
firing = false;
// 如果list是存在的,即改callbacks还没有被禁用
if ( list ) {
// 如果 stack中有值,则递归调用
// 其实这里是判断是否设置了once属性
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) { // 如果设置记忆功能,则清空list(注意,是记忆需要调用的基本信息,即相关参数)
list = [];
} else {
// 只能调用一次,且不能使用memory,
// 则禁用
self.disable();
}
}
},
// 再来看看需要暴露的对象
// Actual Callbacks object
self = {
// 添加方法
// Add a callback or a collection of callbacks to the list
add: function() {
// list其实是可以作为是否禁用的标志的,
// 如果list存在
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?
// 如果正在触发,则只需要更新firingLength
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
// 如果memory,则在添加的时候直接触发
} else if ( memory ) {
firingStart = start;
fire( memory );
}
}
return this;
},
// Remove a callback from the list
// 删除方法,遍历删除指定的方法,并维护好firingLength以及firingIndex
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() {
// 看,这里有用到list是否存在来判断 是否被禁用
return !list;
},
// Lock the list in its current state
// 锁住即不能再被触发
// 如果没有设置memory则直接禁用
lock: function() {
stack = undefined;
if ( !memory ) {
self.disable();
}
return this;
},
// 是否加锁
// Is it locked?
locked: function() {
// 居然是判断stack是否存在
// 由此推断 加锁应该是设置智能触发一次
return !stack;
},
// Call all callbacks with the given context and arguments
fireWith: function( context, args ) {
args = args || [];
// 看这封装了arguments,用来内部fire函数的调用
args = [ context, args.slice ? args.slice() : args ];
// 如果还没被触发,或者允许触发多次
if ( list && ( !fired || stack ) ) {
// 正在触发,则添加到stack
// 在当次触发后,直接触发
if ( firing ) {
stack.push( args );
} else {
// 直接触发
fire( args );
}
}
return this;
},
// Call all the callbacks with the given arguments
// 设置context为this
fire: function() {
self.fireWith( this, arguments );
return this;
},
// To know if the callbacks have already been called at least once
fired: function() {
return !!fired;
}
};
// 注意有一个细节,self的所有方法都是返回的this
// 这表明,它是支持链式操作的
// jquery 很多地方用了这种优雅的技术
return self;
};


  好吧,Callbacks就讲到这里了,神奇而低调的函数队列,在以后的源码中你也会经常看到他的身影,所以他能做什么并不用着急。

但还是举些小例子用用看:

var c = $.Callbacks("once memory");
c.add(function(i) {
alert(123 + '-' + i);
});
c.add(function(i) {
alert(234 + '-' + i);
});
c.add(function(i) {
alert(456 + '-' + i);
});
c.fire('tianxia');
// alert('123-tianxi'); alert('234-tianxi'); alert('456-tianxi');
c.fire();
// 再次调用,啥都没发生,因为设置了once
// 什么都没发生
c.add(function(i) {
alert(i);
});
// alert('tianxia')
// 在设置memory,添加后,直接触发


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: