jQuery 2.0.3 源码分析 回调对象 - Callbacks
源码API:http://api.jquery.com/jQuery.Callbacks/
jQuery.Callbacks()是在版本1.7中新加入的。它是一个多用途的回调函数列表对象,提供了一种强大的方法来管理回调函数队列。
那么jQuery.Callbacks使用场景在哪里?
在很多时候需要控制一系列的函数顺序执行。那么一般就需要一个队列函数来处理这个问题
我们看一段代码
function Aaron(List, callback) {
setTimeout(function() {
var task = List.shift();
task(); //执行函数
if (task.length > 0) { //递归分解
setTimeout(arguments.callee, 1000)
} else {
callback()
}
}, 25)
} Aaron([function(){
alert('a')
},function(){
alert('b')
}],function(){
alert('callback')
})
分别弹出 ‘a’ , ‘b’ ,’callback’
传入一组函数参数,靠递归解析,分个执行,其实就是靠setTimeout可以把函数加入到队列末尾才执行的原理
*****但是这样写,是不是很麻烦?*****
我们换成jQuery提供的方式
var callbacks = $.Callbacks(); callbacks.add(function() {
alert('a');
}) callbacks.add(function() {
alert('b');
}) callbacks.fire(); //输出结果: 'a' 'b'
是不是便捷很多了,代码又很清晰,所以它是一个多用途的回调函数列表对象,提供了一种强大的方法来管理回调函数队列。
同时还提供几个便捷的处理参数
once
: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred).memory
: 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred).unique
: 确保一次只能添加一个回调(所以在列表中没有重复的回调).stopOnFalse
: 当一个回调返回false 时中断调用
var callbacks = $.Callbacks('once'); callbacks.add(function() {
alert('a');
}) callbacks.add(function() {
alert('b');
}) callbacks.fire(); //输出结果: 'a' 'b'
callbacks.fire(); //未执行
once的作用是使callback队列只执行一次
OK,我们大概知道这个是干嘛用的了,可以开始上正菜了。
$.Callbacks是在jQuery内部使用,如为$.ajax,$.Deferred等组件提供基础功能的函数,jQuery在1.5引入了Deferred对象(异步列队),jQuery内部基本所有有异步的代码都被promise所转化成同步代码执行了,后期在讨论了
根据jQuery.Callbacks()的API
提供一下几种方法:
callbacks.add() 回调列表中添加一个回调或回调的集合。
callbacks.disable() 禁用回调列表中的回调
callbacks.disabled() 确定回调列表是否已被禁用。
callbacks.empty() 从列表中删除所有的回调.
callbacks.fire() 用给定的参数调用所有的回调
callbacks.fired() 访问给定的上下文和参数列表中的所有回调。
callbacks.fireWith() 访问给定的上下文和参数列表中的所有回调。
callbacks.has() 确定列表中是否提供一个回调
callbacks.lock() 锁定当前状态的回调列表。
callbacks.locked() 确定回调列表是否已被锁定。
callbacks.remove() 从回调列表中的删除一个回调或回调集合。
我们看官网提供的demo
function fn1( value ) {
console.log( value );
} function fn2( value ) {
fn1("fn2 says: " + value);
return false;
}
可以将上述两个方法作为回调函数,并添加到 $.Callbacks
列表中,并按下面的顺序调用它们:
var callbacks = $.Callbacks();
callbacks.add( fn1 ); // outputs: foo!
callbacks.fire( "foo!" ); callbacks.add( fn2 ); // outputs: bar!, fn2 says: bar!
callbacks.fire( "bar!" );
这样做的结果是,当构造复杂的回调函数列表时,将会变更很简单。可以根据需要,很方面的就可以向这些回调函数中传入所需的参数。
上面的例子中,我们使用了 $.Callbacks()
的两个方法: .add()
和 .fire()
。 .add() 可以向回调函数列表中添加新的回调函数,fire() 可以向回调函数中传递参数,并执行回调函数。
设计思想:
先看官网的demo这个列子,涉及到了 add 与 fire方法,熟悉设计模式的童鞋呢,一眼就看出,其实又是基于发布订阅的观察者模式的设计了
pub/sub (观察者模式) 的背后,总的想法是在应用程序中增强松耦合性。并非是在其它对象的方法上的单个对象调用。一个对象作为特定任务或是另一对象的活动的观察者,并且在这个任务或活动发生时,通知观察者。观察者也被叫作订阅者(Subscriber),它指向被观察的对象,既被观察者(Publisher 或 subject)。当事件发生时,被观察者(Publisher)就会通知观察者(subscriber)
作为 $.Callbacks()
的创建组件的一个演示,只使用回调函数列表,就可以实现 Pub/Sub 系统。将 $.Callbacks
作为一个队列
我来模拟下常规最简单的实现
var Observable = {
callbacks: [],
add: function(fn) {
this.callbacks.push(fn);
},
fire: function() {
this.callbacks.forEach(function(fn) {
fn();
})
}
}
Observable.add(function() {
alert(1)
})
Observable.fire(function() {
alert(2)
})
Observable.fire(); // 1, 2
构建一个存放回调的数组,如this.callbacks= [] 添加回调时,将回调push进this.callbacks,执行则遍历this.callbacks执行回调。
也弹出1跟2了,实际上jQuery.callbacks是如何处理的呢?
我们看源码
整个$.Callbacks的源码很少,它是一个工厂函数,使用函数调用(非new,它不是一个类)创建对象,它有一个可选参数flags用来设置回调函数的行为。、
对外的接口也就是self的返回
self上的add源码


展开
源码
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;
},
其中有一段代码要单独拿出来
//这里用了一个立即执行的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 )
add方法
实参可以是Function, Array
callbacks.add( callbacks )
callbacks
类型: Function, Array
一个函数,或者一个函数数组,用来添加到回调列表。
如果是数组会递归调用私有的add函数 list.push( arg );
发现没,设计的原理上其实跟上面发的简单模式 大同小异
fire方法
外观模式 self.fire –> self.fireWith –> fire
最终执行代码是内部私有的fire方法了


fire方法 // 触发回调函数列表
fire = function( data ) {
//如果参数memory为true,则记录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; // 阻止未来可能由于add所产生的回调
break; //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环
}
}
//标记回调结束
firing = false;
//如果列表存在
if ( list ) {
//如果堆栈存在
if ( stack ) {
//如果堆栈不为空
if ( stack.length ) {
//从堆栈头部取出,递归fire。
fire( stack.shift() );
}
//否则,如果有记忆
} else if ( memory ) {
//列表清空
list = [];
//再否则阻止回调列表中的回调
} else {
self.disable();
}
}
},
最终处理的代码
list[ firingIndex ].apply( data[ 0 ], data[ 1 ] )
其实就是拿出list中保存的回调函数,执行罢了,所以整个设计的原理,还是符合我们开始设想的
具体的实现
$.Callbacks( "once" )
确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred).
var callbacks = $.Callbacks( "once" );
callbacks.add( fn1 );
callbacks.fire( "foo" ); //foo 只执行了一次,后面没执行了
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );
在fire中调用了 self.disable(); 方法
// 禁用回调列表中的回调。
disable: function() {
list = stack = memory = undefined;
return this;
},
$.Callbacks( "memory" )
保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred).
var callbacks = $.Callbacks("memory"); callbacks.add(function() {
console.log("f1");
}); callbacks.fire(); //输出 "f1",这时函数列表已经执行完毕! callbacks.add(function() {
console.log("f2");
}); //memory作用在这里,没有fire,一样有结果: f2
在调用 add() 方法时,如果这时 callbacks队列 满足 fired && firing = false(真执行完毕) && memory(需要在构造函数指定),那么add() 进去的回调函数会立即执行,而这个 add 进去的回调函数调用时的参数存储在 memory 变量中。memory 变量用于存储最后一次调用 callbacks.fireWith(...) 时所使用的参数 [context, arguments]。
$.Callbacks( "unique" )
确保一次只能添加一个回调(所以在列表中没有重复的回调)
var f1 = function() {
console.log("f1");
}; var callbacks = $.Callbacks();
callbacks.add(f1);
callbacks.add(f1);
callbacks.fire(); //输出 f1 f1 //传递参数 "unique"
callbacks = $.Callbacks("unique");
callbacks.add(f1); //有效
callbacks.add(f1); //添加不进去
callbacks.fire(); //输出: f1
****注意add方法默认不去重,比如这里fn1添加两次,fire时会触发两次****
这里处理很简单
if ( !options.unique || !self.has( arg ) ) { //确保是否可以重复
list.push( arg );
} }
在添加的到处理队列时候,判断一下即可
$.Callbacks( "stopOnFalse" )
:
当一个回调返回false 时中断调用
var f1 = function() {
console.log("f1");
return false
}; //注意 return false;
var f2 = function() {
console.log("f2");
}; var callbacks = $.Callbacks();
callbacks.add(f1);
callbacks.add(f2);
callbacks.fire(); //输出 f1 f2 callbacks = $.Callbacks("memory stopOnFalse");
callbacks.add(f1);
callbacks.add(f2);
callbacks.fire(); //只输出 f1 callbacks.add(function() {
console.log("f3");
}); //不会输出,memory已经失去作用了
callbacks.fire(); //重新触发,输出f1
附源码:
jQuery.Callbacks = function( options ) { // Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
//通过字符串在optionsCache寻找有没有相应缓存,如果没有则创建一个,有则引用
//如果是对象则通过jQuery.extend深复制后赋给options。
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为true,则记录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 ) {
// 阻止未来可能由于add所产生的回调
memory = false; // To prevent further calls using add
break; //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环
}
}
//标记回调结束
firing = false;
if ( list ) {
if ( stack ) {
if ( stack.length ) {
//从堆栈头部取出,递归fire
fire( stack.shift() );
}
} else if ( memory ) {//否则,如果有记忆
list = [];
} else {//再否则阻止回调列表中的回调
self.disable();
}
}
},
// Actual Callbacks object
// 暴露在外的Callbacks对象,对外接口
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传进来的列表的每一个对象执行操作
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?
// 如果回调列表中的回调正在执行时,其中的一个回调函数执行了Callbacks.add操作
// 上句话可以简称:如果在执行Callbacks.add操作的状态为firing时
// 那么需要更新firingLength值
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
} else if ( memory ) {
//如果options.memory为true,则将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循环的意义在于借助于强大的jQuery.inArray删除函数列表中相同的函数引用(没有设置unique的情况)
// jQuery.inArray将每次返回查找到的元素的index作为自己的第三个参数继续进行查找,直到函数列表的尽头
// splice删除数组元素,修改数组的结构
while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );
// Handle firing indexes
// 在函数列表处于firing状态时,最主要的就是维护firingLength和firgingIndex这两个值
// 保证fire时函数列表中的函数能够被正确执行(fire中的for循环需要这两个值
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;
};
jQuery.Callbacks() 比较简单,也没什么难点
jQuery.Callbacks() 方法的核心是 fire() 方法,将该 fire() 方法作为私有方法被封装在函数中不可直接访问
因此像 memory、firing、fired 这些状态对于外部上下文来说是不可更改的
还有需要注意的是,如果回调函数中使用了 this 对象,可以直接用这个 this 来访问self对象的公有API。当然,也可以用 fireWith() 自己指定 this 的引用对象。
jQuery.Callbacks()的核心思想是 Pub/Sub 模式,建立了程序间的松散耦合和高效通信。
PS: 路过留点痕。。
jQuery 2.0.3 源码分析 回调对象 - Callbacks的更多相关文章
- jQuery 2.0.3 源码分析Sizzle引擎解析原理
jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理 声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + ...
- jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)
Deferred的概念请看第一篇 http://www.cnblogs.com/aaronjs/p/3348569.html ******************构建Deferred对象时候的流程图* ...
- jQuery 2.0.3 源码分析 Deferred概念
JavaScript编程几乎总是伴随着异步操作,传统的异步操作会在操作完成之后,使用回调函数传回结果,而回调函数中则包含了后续的工作.这也是造成异步编程困难的主要原因:我们一直习惯于“线性”地编写代码 ...
- jQuery 2.0.3 源码分析 事件绑定 - bind/live/delegate/on
事件(Event)是JavaScript应用跳动的心脏,通过使用JavaScript ,你可以监听特定事件的发生,并规定让某些事件发生以对这些事件做出响应 事件的基础就不重复讲解了,本来是定位源码分析 ...
- jQuery 2.0.3 源码分析 事件体系结构
那么jQuery事件处理机制能帮我们处理那些问题? 毋容置疑首先要解决浏览器事件兼容问题 可以在一个事件类型上添加多个事件处理函数,可以一次添加多个事件类型的事件处理函数 提供了常用事件的便捷方法 支 ...
- jQuery 2.0.3 源码分析 Deferrred概念
转载http://www.cnblogs.com/aaronjs/p/3348569.html JavaScript编程几乎总是伴随着异步操作,传统的异步操作会在操作完成之后,使用回调函数传回结果,而 ...
- jQuery 2.0.3 源码分析core - 选择器
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 打开jQuery源码,一眼看去到处都充斥着正则表达式,jQuery框架的基础就是查询了,查询文档元素对象 ...
- jQuery 2.0.3 源码分析Sizzle引擎 - 编译函数(大篇幅)
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 从Sizzle1.8开始,这是Sizzle的分界线了,引入了编译函数机制 网上基本没有资料细说这个东东的,sizzle引入这 ...
- jQuery 2.0.3 源码分析 数据缓存
历史背景: jQuery从1.2.3版本引入数据缓存系统,主要的原因就是早期的事件系统 Dean Edwards 的 ddEvent.js代码 带来的问题: 没有一个系统的缓存机制,它把事件的回调都放 ...
随机推荐
- SOAPUI使用教程-REST服务和WADL
首先创建一个新的REST项目: 选择文件|新建项目REST从主菜单: 通常情况下,我们可能会只提供一个URI 点击导入消耗. 在新建项目消耗对话框: 点击浏览. 然后,我们可以浏览到该文件: 点击 ...
- log4j使用--http://www.cnblogs.com/eflylab/archive/2007/01/11/618001.html
package log4jTest.com; import java.io.FileReader; import org.apache.log4j.BasicConfigurator; import ...
- C++文件操作(fstream)
C++ 通过以下几个类支持文件的输入输出: ofstream: 写操作(输出)的文件类 (由ostream引申而来) ifstream: 读操作(输入)的文件类(由istream引申而来) fstre ...
- window下xampp配置多端口、多站点步骤
好些日子没整理知识了,许多新东西不整理出来时间一长就淡忘了.看来以后得继续坚持整理. 配置XAMPP多端口.多站点如下步骤: 多端口: (一个域名下同时配置多个端口,从而达到访问不同程序) 效果例如: ...
- tornado 学习笔记16 HTTP1Connection
HTTP/1.x协议的具体实现.实现HTTPConnection接口. 16.1 构造函数 定义: def __init__(self, stream, is_client, params=None, ...
- linux shell重定向总结
command-line1 [-n] > file或文件操作符或设备 command-line1 [-n] >> file或文件操作符或设备 >suc.txt >err. ...
- JQuery实现Ajax应用
将自己之前在印象笔记的笔记搬家了~ 1.使用 load()方法异步请求数据,通过Ajax 请求加载服务器中的数据,并把返回的数据放置到指定的元素中,它的调用格式为: load(url,[data],[ ...
- highchart 添加新的series
code: <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" c ...
- 关于C#的微信开发的入门记录一
在之前老是看到一些微信开发的例子,但是作为初学者会有很多问题,之前我也找了很多帖子,但是最终也没能解决,现在刚好手里有一个项目,总结一下分享给准备做却动不了手的朋友们,本文只是以我个人的经验作为浅谈( ...
- Python之路【第三篇】python基础 之基本数据类型 补充
字符串格式化 Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存.[PEP-310 ...