jQuery.Callbacks 源码解读二
一、参数标记
/*
* once: 确保回调列表仅只fire一次
* unique: 在执行add操作中,确保回调列表中不存在重复的回调
* stopOnFalse: 当执行回调返回值为false,则终止回调队列的执行
* momery: 记录上一次fire时的参数,并在add中传递给fire和执行fire,执行时firingIndex为上一次fire时的firingLength
*/
二、源码解读分析
var optionsCache = {},
// Used for splitting on whitespace
core_rnotwhite = /\S+/g;
// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
// 多个变量指向同一对象(或数组)引用时,其中一个变量修改了被引用对象的内部结构,其他引用变量也会表现出来
var object = optionsCache[ options ] = {};
jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true; // optionsCache[ options ][ flag ] = true;
});
return object;
}
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 ]
( optionsCache[ options ] || createOptions( options ) ) :
// 说明也可以这样$.Callbacks({once:true, memory:true})使用
jQuery.extend( {}, options );
var // Flag to know if list is currently firing
firing,
// Last fire value (for non-forgettable lists)
memory,
// Flag to know if list was already fired
fired,
// End of the loop when firing
firingLength,
// Index of currently firing callback (modified by remove if needed)
firingIndex,
// First callback to fire (used internally by add and fireWith)
firingStart,
// Actual callback list
list = [],
// Stack of fire calls for repeatable lists
stack = !options.once && [],
// Fire callbacks
// data为fireWith内部整理的args数组
fire = function( data ) {
memory = options.memory && data;
fired = true;
// 处理在add中,options.memory = true;的情况
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true;
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
// 正在执行的回调返回值为false 且 options.stopOnFalse为true,则终止回调队列的执行
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if ( list ) {
// 处理正在执行的回调中执行fireWith的操作;
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
}
// 上一分支状态为回调执行过,且可以执行多次
// 此时 options.once = true; 这里将list设置为[],只是确保下次执行fire时,无回调执行
// 但是如果 options.memory = true; 仍然会执行add中的fire操作,因为此时回调列表中已有回调
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" ) {
// 回调不唯一 或 唯一且不存在,则push
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?
// 正在执行的回调执行了add操作,则更新firingLength
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
// 如果options.memory为true,则再次执行fire,且参数相同,fire中的firingIndex为此时的firingStart
}
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
// 正在执行的回调执行了remvoe操作,则更新firingLength和firingIndex的值
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 = [];
return this;
},
// Have the list do nothing anymore
// 禁用add,remove,fire主要方法的工作
disable: function() {
list = stack = memory = undefined;
return this;
},
// Is it disabled?
disabled: function() {
return !list;
},
// Lock the list in its current state
lock: function() {
// 如果回调执行过,则将阻止self.fire操作
// 但如果 options.memory = true,则仍然会执行fire操作
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 ) ) {
// 正在执行的回调函数执行了fireWith操作( 暗指回调列表已执行过,且可以执行多次,stack = []; )
// 该函数需要条件执行,或有移除该函数的操作,否则陷入死循环,详见例2
if ( firing ) {
stack.push( args );
}
// 正在执行的回调函数没有执行fireWith操作
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;
};
三、示例
例1:

$(function(){
// 定义三个将要增加到回调列表的回调函数fn1,fn2,fn3
function fn1(arg){
console.log( 'fn1 says:' + arg );
// 在fn1中执行Callbacks.add操作,此时Callbacks函数内部的firingLength将会得到更新
$callbacks.add(fn2);
}
function fn2(arg){
console.log( 'fn2 says:' + arg );
}
function fn3(arg){
console.log( 'fn3 says:' + arg );
}
// Callbacks传递了memory
// 也可以这样使用$.Callbacks({ memory: true });
var $callbacks = $.Callbacks('memory');
// 将fn1增加到回调列表中,因为在fn1中有执行了add(fn2)操作,因此回调列表中的回调为fn1,fn2
$callbacks.add(fn1);
// output: fn1 says:foo
// output: fn2 says:foo
$callbacks.fire('foo');
// 将之前fire的参数传递给最近增加的回调fn3,并执行fn3
// output: fn3 says:foo
$callbacks.add(fn3);
// 再执行一次fire,注意此时回调列表中的回调一次是fn1,fn2,fn3,fn2
// output: fn1 says:baz
// output: fn2 says:baz
// output: fn3 says:baz
// output: fn2 says:baz
// 如果期望回调列表中只有fn1,fn2,fn3,只需在Callbacks函数中传入unique
$callbacks.fire('baz');
});
例2

$(function(){
function fn1(arg){
console.log( 'fn1 says:' + arg );
}
function fn2(arg){
console.log( 'fn2 says:' + arg );
$callbacks.fireWith(window, ['yjh']);
// 一定要执行这一步,否则将会陷入死循环
$callbacks.remove(fn2);
}
var $callbacks = $.Callbacks();
$callbacks.add(fn1);
// output: fn1 says:foo
$callbacks.fire('foo');
$callbacks.add(fn2);
// output: fn1 says:baz
// output: fn2 says:baz
// output: fn1 says:yjh
$callbacks.fire('baz');
});
PS:
此前写过一篇关于jQuery.Callbacks源码分析的随笔,理解不透彻,今天又重新翻阅了一下,记录一下自己的源码阅读,相比之前,感觉好多了。
阅读前,可以先看API,弄清楚四个参数标志,'once', 'memory', 'unique', 'stopOnFalse', 简单的执行add, fire操作,然后再看源码;
阅读顺序:
1、先阅读var声明的变量,fire函数的前半部分,self对象中的add, remove函数,有些难以理解暂时往下看;
2、然后阅读self对象中的fire,fireWith,最后再来阅读fire函数,弄清楚后再看其他self对象中的方法。
jQuery.Callbacks 源码解读二的更多相关文章
- (转)go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin
转自:http://www.baiyuxiong.com/?p=886 ---------------------------------------------------------------- ...
- mybatis源码解读(二)——构建Configuration对象
Configuration 对象保存了所有mybatis的配置信息,主要包括: ①. mybatis-configuration.xml 基础配置文件 ②. mapper.xml 映射器配置文件 1. ...
- jQuery toggleClass 源码解读
toggleClass: function( value, stateVal ) { var type = typeof value;//值类型 if ( typeof stateVal === &q ...
- jQuery attr() 源码解读
我们知道,$().attr()实质上是内部调用了jQuery.access方法,在调用时jQuery.attr作为回调传入.在通过种种判断(参看jQuery.access()方法)之后,取值和赋值最后 ...
- ConcurrentHashMap源码解读二
接下来就讲解put里面的三个方法,分别是 1.数组初始化方法initTable() 2.线程协助扩容方法helpTransfer() 3.计数方法addCount() 首先是数组初始化,再将源码之前, ...
- go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin
nsqlookupd: 官方文档解释见:http://bitly.github.io/nsq/components/nsqlookupd.html 用官方话来讲是:nsqlookupd管理拓扑信息,客 ...
- vue2.0 源码解读(二)
小伞最近比较忙,阅读源码的速度越来越慢了 最近和朋友交流的时候,发现他们对于源码的目录结构都不是很清楚 红色圈子内是我们需要关心的地方 compiler 模板编译部分 core 核心实现部分 ent ...
- ROS源码解读(二)--全局路径规划
博客转载自:https://blog.csdn.net/xmy306538517/article/details/79032324 ROS中,机器人全局路径规划默认使用的是navfn包 ,move_b ...
- Python Web Flask源码解读(二)——路由原理
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
随机推荐
- 《ODAY安全:软件漏洞分析技术》学习心得-----shellcode的一点小小的思考
I will Make Impossible To I'm possible -----------LittleHann 看了2个多星期.终于把0DAY这本书给看完了,自己动手将书上的实验一个一个实现 ...
- C++ essentials 之 static 关键字
extraction from The C++ Programming Language, 4th. edition, Bjarne Stroustrup If no initializer is s ...
- 第二次作业——C++学习
课程选择: 以往在自学的过程就比较留意一些自学的网站,所以这次"C++自学"感觉找课程还是比较轻松的. 因为之前网页等学习都是在慕课网(视频学习个人感觉有时挺费时间的,特别是有时以 ...
- 重写Object类中的equals方法
Object是所有类的父亲,这个类有很多方法,我们都可以直接调用,但有些方法并不适合,例如下面的student类 public class Student { //姓名.学号.年纪 private S ...
- ThreadPoolExecutor机制
一.概述 1.ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等等服务: 2.Execu ...
- 常见linux命令释义(第四天)——bash部分
学linux的时候,我跳过了一些很重要的东西.比如分区.还有vim的深入学习.分区没有学习是因为我装的是虚拟机,不知道是什么原因,格式化分区不能正常显示.至于vim,简单的增删改查我已经了解了.能够顺 ...
- 利用jar命令打包和解压
常常在网上看到有人询问:如何把 java 程序编译成 .exe 文件.通常回答只有两种,一种是制作一个可执行的 JAR 文件包,然后就可以像.chm 文档一样双击运行了:而另一种是使用 JET 来进行 ...
- CSS3-box-flex弹性盒布局
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- MyISAM与InnoDB两者之间怎么选择
1.MyISAM不支持事务,InnoDB是事务类型的存储引擎 当我们的表需要用到事务支持的时候,那肯定是不能选择MyISAM了. 2.MyISAM只支持表级锁,BDB支持页级锁和表级锁默认为页级锁,而 ...
- mysql dump
mysqldump wifi3 > wifi3.sql mysqldump wifi3 < wifi3.sql