jQuery 源码解析(八) 异步队列模块 Callbacks 回调函数详解
异步队列用于实现异步任务和回调函数的解耦,为ajax模块、队列模块、ready事件提供基础功能,包含三个部分:Query.Callbacks(flags)、jQuery.Deferred(funct)和jQuery.when()。本节讲解Callbacks,也就是回调函数列表
回调函数用于管理一组回调函数,支持添加、移除、触发、锁定和禁用回调函数,为jQuery.ajax、jQuery.Deferred()和ready()事件提供基础功能,我们也可以基于它编写新的组件。
使用方法:$.Callbacks(flags),flags是一个参数,用于指定传递的标记,可以为空,可以设置为以下四个选项之一,或者任意组合也可以,如下:
unique 确保一个回调函数只能被添加一次
stopOnFlase 当某个回调函数返回false时中断执行
once 确保回调函数列表只能被触发一次
memory 记录上一次触发回调函数列表时的参数,之后添加的任何函数都将用记录的参数值立即调用
执行成功后返回一个对象,该函数含有如下几个方法:
add(fn/arr) 添加一个/多个回调函数到list数组中
remove(fn1,fn2,fn3...) 从list中移除多个回调函数
empty() 清空list数组
disable() 禁用列表,使他不再做任何事情,该操作不可还原
disabled() 判断是否已禁用列表,如果已经禁用了则返回true
lock() 锁定memory模式下的回调函数的上下文和参数
locked() 判断回调函数列表是否已被锁定
fireWith(content,args) 以content为上下文,args为上下文,执行所有函数列表
fire(arguments) 指定上下文为当前回调函数列表来调用fireWith
fired() 通过检测变量memory的值来判断回调函数列表是否被触发过
这些方法归纳起来就是新增/移除/触发回调函数,还有几个是查看当前的状态的(是否已触发、是否被禁用等)
$.Callbacks()可以传入任意组合,也可以为空,例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
<script>
function fn1(val){console.log('fn1 says:' + val);}
function fn2(val){console.log('fn2 says ' + val);}
function fn3(val){console.log('fn3 says ' + val);} var cbs = $.Callbacks(); //创建Callbacks对象
cbs.add([fn1,fn2,fn3]); //添加函数到回调函数列表中
cbs.fire('test1'); //触发回调函数,参数是test1 输出:fn1 says:test1、fn2 says:test1和fn3 says:test
cbs.remove(fn1,fn3); //移除回调函数fn1,fn3
cbs.fire('test2'); //触发回调函数,输出:fn2 says test2
</script>
</body>
</html>
输出如下:

这里我们定义了一个Callback(),没有传入任何参数,比较常用的是once和memory的组合,例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
<script>
function fn1(val){console.log('fn1 says:' + val);}
function fn2(val){console.log('fn2 says ' + val);}
function fn3(val){console.log('fn3 says ' + val);} var cbs = $.Callbacks('once memory');
cbs.add([fn1,fn2]);
cbs.fire('test'); //输出:fn1 says:test和fn2 says:test
cbs.fire('test'); //没有输出,因为设置了once标志,当调用cbs.fire('test')后就把list清空了
cbs.add(fn3);
</script>
</body>
</html>
输出如下:

如果传入once+memory的组合,这时回调函数被触发后再调用fire()去触发时是不会执行回调函数了的,因为当第一次fire()触发回调函数后,如果由once标记就把内部的list设为了undefined,可以理解为把list给禁用了,加入了memory标记的话当执行fire()时jQuery内部会把当时的上下文和参数保存起来,这样下次直接添加回调函数就会自动回掉函数了
源码分析
writer by:大沙漠 QQ:22969969
$.Callbacks()的实现原理很简单,Callbacks是jQuery内部的一个函数,第一次执行时该函数会把传入的标记给缓存起来,通过作用域保存在内部的一个变量中,之后调用add()添加函数时也会一个个的缓存起来,最后调用fire()触发回调函数时会遍历这些回调函数列表,一个个的去触发,中间根据不同的标记做不同的处理。
$.Callbacks是直接定义在内部的jQuery上的,大致如下
jQuery.Callbacks = function( flags ) { //在jQuery上添加一个Callbacks方法
// Convert flags from String-formatted to Object-formatted
// (we check in cache first)
flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; //先尝试从缓存对象flagsCache中获取标记字符串flags对应的标记对象。如果没找到再调用工具函数createFlags()创建标记
var // Actual callback list
list = [],
// Stack of fire calls for repeatable lists
stack = [],
// Last fire value (for non-forgettable lists)
memory,
// 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,
// Add one or several callbacks to the list
add = function( args ) {
/*add 是一个工具函数,用于添加回调函数*/
},
// Fire callbacks
fire = function( context, args ) {
/*fire也是一个工具函数,用于触发回调函数列表*/
},
// Actual Callbacks object
self = {
/*对象的定义*/
};
return self; //最后返回self,这是一个对象,该对象内定义的属性也就是对外的接口,供我们使用的
};
createFlags用于将字符串格式的标记转换为对象格式的标记,如下:
function createFlags( flags ) { //将字符串格式的标记转换为对象格式的标记
var object = flagsCache[ flags ] = {}, //初始化object和flagsCache[flags]为空对象
i, length;
flags = flags.split( /\s+/ ); //对flags用空格分隔
for ( i = 0, length = flags.length; i < length; i++ ) { //遍历每个标记
object[ flags[i] ] = true; //属性值一律设为true,这里访问对象用方括号表示法,可以用变量来表示对象的属性。
}
return object;
}
以上面的第二个例子为例(once+memory),执行到这里返回后对应的如下:

然后返回内部的self对象,这样$.Callbacks()就执行完毕了,后面我们调用add添加回调函数时会执行self内的add函数,如下:
add: function() { //添加回调函数
if ( list ) {
var length = list.length;
add( arguments ); //用工具函数add添加回调函数
// 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, unless previous
// firing was halted (stopOnFalse)
} else if ( memory && memory !== true ) {
firingStart = length;
fire( memory[ 0 ], memory[ 1 ] );
}
}
return this;
},
这里红色标记的add是在self对象同作用域的add,这是一个工具函数,如下:
add = function( args ) { //添加一个或多个回调函数到数组list中
var i,
length,
elem,
type,
actual;
for ( i = 0, length = args.length; i < length; i++ ) { //遍历参数
elem = args[ i ]; //第一个参数的内容
type = jQuery.type( elem ); //第一个参数的类型 ;是array或者function
if ( type === "array" ) { //如果参数是数组
// Inspect recursively
add( elem ); //则递归调用自身
} else if ( type === "function" ) { //如果参数是函数
// Add if not in unique mode and callback is not in
if ( !flags.unique || !self.has( elem ) ) { //如果不是unique模式(该模式一个函数只能添加一次,就是回调函数列表中没有重复值),或者 是unique模式但未添加过该函数。
list.push( elem ); //添加args[i]到数组list中
}
}
}
},
最后会push到list中,也就是上一层作用域的list中,例子里执行到这里list中的数据如下:

这样三个函数都被缓存起来了,最后调用fire()触发回调函数时会执行self内的fire()函数,如下:
fire: function() {
self.fireWith( this, arguments ); //指定上下文为当前回调函数列表来调用fireWith(context, args)
return this;
}
fire()会将当前this作为参数,直接调用fireWith,fireWith如下:
fireWith: function( context, args ) { //使用指定的上下文和参数触发回调函数列表中的所有回调函数
if ( stack ) {
if ( firing ) { //如果回调函数正在执行当中
if ( !flags.once ) {
stack.push( [ context, args ] );
}
} else if ( !( flags.once && memory ) ) { //如果回调函数未在执行中,并且不是已经触发过的once模式,
fire( context, args ); //调用工具函数fire(context, args )执行所有函数
}
}
return this;
},
最后会调用fire,也就是self同作用域的工具fire函数,如下:
fire = function( context, args ) { //使用指定的上下文context和参数args调用数组list中的回调函数
args = args || [];
memory = !flags.memory || [ context, args ]; //如果当前不是memory模式,则设置memory为ture,表示当前函数回调函数列表被触发过。如果当前回调函数是memory模式,设置momory为[context,args],除了表示当前函数回调函数列表被触发过,还能保存上下文和参数。
firing = true; //把firing设为ture,表示回调函数正在执行当中
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
for ( ; list && firingIndex < firingLength; firingIndex++ ) { //如果没有禁用列表则循环执行每个函数
if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { //执行list数组里的每一个函数,如果开启了stopOnFalse标记且有一个回调函数返回false
memory = true; // Mark as halted //则把memory设置为ture(清除上下文),且退出余下所有函数列表的执行。
break;
}
}
firing = false;
if ( list ) { //如果没有禁用列表
if ( !flags.once ) { //如果不是once模式,即可多次触发回调函数列表
if ( stack && stack.length ) {
memory = stack.shift();
self.fireWith( memory[ 0 ], memory[ 1 ] );
}
} else if ( memory === true ) { //如果(是once模式,而且不是memory模式) 或者 (是once+memory 且设置了stopOnFlase模式,并且某个回调函数返回了false)
self.disable(); //则禁用回调函数列表
} else { //如果是once模式+memory模式
list = []; //则清空数组list,后续添加的回调函数还会立即执行。
}
}
},
fire()执行完后回调函数列表就执行完毕了,中间通过一些标记做处理,如果由传入memory,则会保存上下文,下次通过add添加回调函数时会立即执行的
jQuery 源码解析(八) 异步队列模块 Callbacks 回调函数详解的更多相关文章
- jQuery源码分析(九) 异步队列模块 Deferred 详解
deferred对象就是jQuery的回调函数解决方案,它解决了如何处理耗时操作的问题,比如一些Ajax操作,动画操作等.(P.s:紧跟上一节:https://www.cnblogs.com/grea ...
- jQuery 源码解析(三十) 动画模块 $.animate()详解
jQuery的动画模块提供了包括隐藏显示动画.渐显渐隐动画.滑入划出动画,同时还支持构造复杂自定义动画,动画模块用到了之前讲解过的很多其它很多模块,例如队列.事件等等, $.animate()的用法如 ...
- jQuery 源码解析(三十一) 动画模块 便捷动画详解
jquery在$.animate()这个接口上又封装了几个API,用于进行匹配元素的便捷动画,如下: $(selector).show(speed,easing,callback) ;如 ...
- JQuery源码解析(一)
写在前面:本<JQuery源码解析>系列是基于一些前辈们的文章进行进一步的分析.细化.修改而写出来的,在这边感谢那些慷慨提供科普文档的技术大拿们. 要查阅JQ的源文件请下载开发版的JQ.j ...
- jquery源码解析:代码结构分析
本系列是针对jquery2.0.3版本进行的讲解.此版本不支持IE8及以下版本. (function(){ (21, 94) 定义了一些变量和函数, jQuery = function() ...
- jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究
终于动笔开始 jQuery 源码解析第二篇,写文章还真是有难度,要把自已懂的表述清楚,要让别人听懂真的不是一见易事. 在 jQuery 源码解析一:jQuery 类库整体架构设计解析 一文,大致描述了 ...
- jQuery 源码解析一:jQuery 类库整体架构设计解析
如果是做 web 的话,相信都要对 Dom 进行增删查改,那大家都或多或少接触到过 jQuery 类库,其最大特色就是强大的选择器,让开发者脱离原生 JS 一大堆 getElementById.get ...
- jquery 源码解析
静态与实力方法共享设计 遍历方法 $(".a").each() //作为实例方法存在 $.each() //作为静态方法存在 Jquery源码 jQuery.prototype = ...
- JQuery源码解析(九)
jQuery回调对象 jQuery.Callbacks一般开发者接触的很少,虽然jQuery向开发者提供了外部接口调用,但是$.Callbacks()模块的开发目的是为了给内部$.ajax() 和 $ ...
随机推荐
- wpf datetime format
<Style TargetType="{x:Type DatePickerTextBox}"> <Setter Property="Control.Te ...
- python3的reload(sys)
import sys reload(sys) sys.setdefaultencoding(‘utf-8’) 以上是python2的写法,但是在python3中这个需要已经不存在了,这么做也不会什么实 ...
- Java生鲜电商平台-生鲜供应链(采购管理)
Java生鲜电商平台-生鲜供应链(采购管理) 在生鲜供应链系统中采购中心这一模块,它是电商公司管理采购的模块,包含供应商管理,采购订单管理,采购商品管理,在该模块中采购订单是采购中心的核心模块.在其他 ...
- 图说真实上海IT圈:张江男VS漕河泾男
图说上海真实IT圈:张江男VS漕河泾男 架构师修炼宝典 Java 通过比较上海各住宅小区在工作日晚餐与夜宵时段一人食外卖订单指数我们会发现: 上海IT圈两大胜地: 张江高科和漕河泾双双上榜 其中张 ...
- 解决sublime快捷键回车换行问题
鼠标右键sublime 以管理员身份运行 打开首选项里面的按键绑定用户 将下面的代码粘贴复制 { "keys": ["enter"], "comman ...
- Sublime设置格式化代码快捷键ctrl+shift+r
1.以管理员身份运行sublime 2.首选项---按键绑定-用户,将以下代码复制即可(这里注意不要忘记在最后一行添加逗号哦) { "keys": ["ctrl+shif ...
- 在Dynamics CRM中自定义一个通用的查看编辑注释页面
关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复162或者20151016可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me! 注释在CRM中的显示是比较特别, ...
- Android 蓝牙开发(1)
普通蓝牙设备官方文档 Android 平台包含蓝牙网络堆栈支持,凭借此支持,设备能以无线方式与其他蓝牙设备交换数据.应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径. ...
- 剑指offer 12:二进制中1的个数
题目描述 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 解法一:设置标志为flag=1,逐个位移至不同位置,比较是否为1. C++实现 class Solution { publi ...
- iOS 禁用`URL Scheme`和`Universal Link`(通用链接)
为什么要禁用URL Scheme和Universal Link(通用链接) 通常我们APP中都会嵌套一些web页面,有时我们的web页面会被DNS劫持从而跳转到其他APP中:或者是某些APP的Univ ...