jQuery-1.9.1源码分析系列(五) 回调对象
jQuery.Callbacks()提供的回调函数队列管理本来是延时回调处理的一部分,但是后面将其独立出来作为一个模块。jQuery就是这样,各个模块间的代码耦合度是处理的比较好的,值得学习。虽然是从延时回调处理中独立出来的,但是它的功能非常强大,提供了一种强大的方法来管理回调函数队列。
大家都明白封装函数的目的:去耦合与简化操作。
通常情况下函数队列的处理方式
//执行函数 function runList(arr){for(var i = 0; i < arr.length; i++){arr[i](); } arr.length = 0; }var list = []; //添加函数队列 list[list.length] = function(){alert(1)}; list[list.length] = function(){alert(2)};list[list.length] = function(){alert(3)};
//执行 runList(list);//三个函数顺序执行
使用$.callbacks封装以后的处理为
var callbacks = $.Callbacks("unique");callbacks.add( function(){alert(1)} ); callbacks.add( function(){alert(2)} ); callbacks.add( function(){alert(3)} ); //执行 callbacks.fire();//三个函数顺序执行
干净了很多。而且代码可读性比最开始的那个要好很多。list[list.length]神马的最讨厌了。还有主要的是$.callbacks有四个属性可以组合,这个组合可就很强大了。
a. Callbacks的四个可设置的属性分析
once: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred).
设置“once”在执行第一次fire后会直接禁用该Callbacks(fire函数代码段else {self.disable();})
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks('once');callbacks.add(f1);//无执行结果,添加一个回调 callbacks.fire(1);//执行结果1。清除回调列表 callbacks.add(f1);//没有添加回调直接返回 callbacks.fire(2);//无执行结果 callbacks.add(f1);//没有添加回调直接返回 callbacks.fire(3);//无执行结果
memory: 保持以前的值(参数),将函数添加到这个列表的后面,并使用先前保存的参数立即执行该函数。 内部变量会保存上次执行的场景。
他有一个特点,就是在第一次fire之前使用add添加的回调都不会马上执行,只有调用了一次fire之后使用add添加的回调会马上执行。该设置本身不会清除之前的回调列表。
需要注意的是每次add内部执行fire函数都会将firingStart置为0,只有下次add的时候会从新设置firingStart的值。
eg:
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks("memory");callbacks.add( fn1 );//无执行结果 callbacks.fire( "1" );//执行结果1。保存场景参数1 callbacks.add( fn1 );//执行结果1。使用上次保存的场景参数1 callbacks.fire( "2" );//执行结果2,2。保存场景参数2 callbacks.add( fn1 );//执行结果2。使用上次保存的场景参数2 callbacks.fire( "3" );//执行结果3,3,3。保存场景参数3 callbacks.add( fn1 );//执行结果3。使用上次保存的场景参数3 callbacks.fire( "4" );//执行结果4,4,4,4。保存场景参数4
组合使用,组合使用中间使用空格隔开
设置“once memory”, options.once=options.memory=true。在执行第一次fire后会把回到列表清空,而且之后每次add马上执行后页同样会把回调列表清空(fire函数代码段else if ( memory ) {list = [];})。
eg:
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks('once memory');callbacks.add(f1);//无执行结果,添加一个回调 callbacks.fire(1);//执行结果1。清除回调列表,保存场景参数1 callbacks.add(f1);//添加一个回调并执行结果1,使用上次保存的场景参数。清除回调列表 callbacks.fire(2);//无执行结果 callbacks.add(f1);//添加一个回调并执行结果1,使用上次保存的场景参数。清除回调列表 callbacks.fire(3);//无执行结果
两个设置之间用空格,不支持其他符号,比如设置“once,memory”等同于没有设置。
eg:
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks('once,memory');callbacks.add(f1);//无执行结果,添加一个回调 callbacks.fire(1);//执行结果1 callbacks.add(f1);//添加一个回调 callbacks.fire(2);//执行结果2,2 callbacks.add(f1);//添加一个回调 callbacks.fire(3);//执行结果3,3,3 callbacks.add(f1);//添加一个回调 callbacks.fire(4);//执行结果4,4,4,4
unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调).
stopOnFalse: 当一个回调返回false 时中断调用
当有一个回调返回false的时候,会设置memory为false。导致memory失去作用(后续add的函数不会马上执行,当然先前memory保证了前面执行过得函数不再执行这也条也就不起作用了。下次fire会从回调列表的第一个开始执行)。
b. 整体结构
使用缓存是jQuery中最常见的技巧。$.Callbacks中也不例外。主要是缓存Callbacks中遇到的选项(字符串)。
// 使用过的选项缓存 var optionsCache = {};// 新增和缓存回调设置于optionsCache中 function createOptions( options ) {var object = optionsCache[ options ] = {};jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {object[ flag ] = true;});return object; }jQuery.Callbacks = function( options ) {// 尽可能读取缓存,没有则新增缓存options = typeof options === "string" ?( optionsCache[ options ] || createOptions( options ) ) :jQuery.extend( {}, options );var // 回调列表正在执行(为true的时候)的标志 firing,// 最后执行的值(为memory选项下保存) memory,// 回调已经被执行过的标志 fired,// 循环执行回调列表的结束位置 firingLength,// 当前真正执行的回调的索引值 (执行下个回调的时候回更改【如果必要的话】) firingIndex,// 循环执行回调列表的开始位置(在函数add和fireWith中使用) firingStart,// 回调列表list = [],// Stack记录要重复执行的回调列表stack = !options.once && [],// data数组一般第一个元素是上下文环境,第二个元素是参数//执行回调列表fire = function( data ) {…},// 回调对象self = {// 添加回调add: function() {…},// 移除回调remove: function() {…},...// 给定 context 和 arguments执行所有回调fireWith: function( context, args ) {args = args || [];//组装args,第一个元素为上下文环境,第二个元素为参数列表args = [ context, args.slice ? args.slice() : args ];//有list且函数列表没有被执行过或者存在要循环执行的函数列表if ( list && ( !fired || stack ) ) {//如果正在fire,则把函数场景记录在stack中if ( firing ) {stack.push( args );//否则,至此那个fire} else {fire( args );}}return this;},// 使用给定的arguments执行所有回调fire: function() {self.fireWith( this, arguments );return this;},...};return self; };
下面分析两个最重要的两个函数,添加回调函数add和执行回调函数fire
c. add:添加回调
添加回调函数比较简单,针对可能传递的值(函数或者函数数组)将回调添加到回调列表中即可,这里使用了一个闭包,使用了外部变量list。
(function add( args ) {jQuery.each( args, function( _, arg ) {var type = jQuery.type( arg );if ( type === "function" ) {//当$.Callbacks('unique')时,保证列表里面不会出现重复的回调if ( !options.unique || !self.has( arg ) ) {list.push( arg );}//如果是数组则递归添加} else if ( arg && arg.length && type !== "string" ) {add( arg );}});})( arguments );
但是这里需要对用户初始化设置的属性做一些特殊的处理。
如果列表没有定义或null(一般只有在用户设置once且执行过一次后list才会白置为未定义),直接返回list
//如果列表没有定义或null(一般只有在用户设置once且执行过一次后list才会白置为未定义)if ( list ) {...}return this;
当有回调真正执行的时候,需要重新设定回调列表的结束位置firingLength,使后续添加的函数也会执行。实际上这个功能很受争议,不过正常情况一般不会出现添加函数的时候正在执行某个回调。
还有一个比较重要的判断:对于设置了'memory'选项并fire过了回调列表,并且没有还在等待中的回调要fire,则应当马上执行新添加的回调(执行fire(memory))
// 如果正在fire,则设定要执行结束的点firingLength,使后续添加的函数最后不会执行if ( firing ) {firingLength = list.length;// 对于memory(设置了'memory' option并fire过了,memory才能通过该else if语句),//如果没有回调真正fire,应当马上执行fire(memory)。} else if ( memory ) {//这里保证了前面执行过得函数不再执行firingStart = start;fire( memory );}
完整的源码如下
add: function() {//如果列表没有定义或null(一般只有在用户设置once且执行过一次后list才会白置为未定义)if ( list ) {// 保存当前list长度,为memory处理备用var start = list.length;(function add( args ) {jQuery.each( args, function( _, arg ) {var type = jQuery.type( arg );if ( type === "function" ) {//当$.Callbacks('unique')时,保证列表里面不会出现重复的回调if ( !options.unique || !self.has( arg ) ) {list.push( arg );}//如果是数组则递归添加} else if ( arg && arg.length && type !== "string" ) {add( arg );}});})( arguments );// 如果正在fire,则设定要执行结束的点firingLength,使后续添加的函数最后执行if ( firing ) {firingLength = list.length;// 对于memory(设置了'memory' option并fire过了,memory才能通过该else if语句),//如果我们后续没有fire,应当马上执行fire(memory)。} else if ( memory ) {//这里保证了前面执行过得函数不再执行firingStart = start;fire( memory );}}return this; }
View Code
d. fire函数详解
该函数执行回调,最终执行代码段为
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {memory = false; // 阻止未来可能由于add所产生的回调break; }
fire = function( data ) { //有memory才给memory赋值当前场景datamemory = options.memory && data;fired = true;firingIndex = firingStart || 0; //每次fire后都会重置成0,下次$.callbacks.fire调用都会从0开始。当然设置为‘memory’使用add函数内部fire会设置firingStart的值导致回调函数列表执行起始位置更改firingStart = 0;firingLength = list.length;firing = true; //函数开始执行从firingStart到firingLength的所有函数 for ( ; list && firingIndex < firingLength; firingIndex++ ) { //执行firingIndex对应的函数,如果设置是遇到false返回就停止,则设置memory,阻止后续函数执行 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {memory = false; // 阻止未来可能由于add所产生的回调 break;}} //标记回调结束firing = false;//如果列表存在 if ( list ) { //如果堆栈存在(一般没有设置once的时候都进入该分支) if ( stack ) { //如果堆栈不为空 if ( stack.length ) { //执行stack中第一个元素fire( stack.shift() );} //如果有记忆,则清空列表(在设置为once且memory的时候会进入到此分支)} else if ( memory ) {list = []; //禁用回调,该callbacks将不可用,将list/stack/memory都设为未定义} else {self.disable();}} },
真正重要的是执行完成回调以后的处理
//如果列表存在if ( list ) {//如果堆栈存在(一般没有设置once的时候都进入该分支)if ( stack ) {//如果堆栈不为空if ( stack.length ) {//执行stack中第一个元素 fire( stack.shift() );}//如果有记忆,则清空列表(在设置为once且memory的时候会进入到此分支)} else if ( memory ) {list = [];//禁用回调,该callbacks将不可用,将list/stack/memory都设为未定义} else {self.disable();}}
View Code
首先看最外层的判断
if ( list ){... }
什么时候会进不了这个分支呢?唯有当self.disable()被调用的时候,下一次fire就进入不了这个分支。查看self.disable源码
disable: function() {list = stack = memory = undefined;return this;}
根据里面的判断唯有当options选项有once,并且选项中没有memory或选项中有stopOnFalse且执行的回调返回false。这个时候回进入到里面的分支直接将整个回调禁用掉。
//禁用回调,该callbacks将不可用,将list/stack/memory都设为未定义} else {self.disable();}
第一个内部分支if ( stack )主要是选项中没有once就进入。
第二个内部分支只有在选项至少有once和memory的时候才会进入。当然,如果还有stopOnFalse且执行的回调返回false会进入到第三个分支。
//如果有记忆,则清空列表(在设置为once且memory的时候会进入到此分支)} else if ( memory ) {
好了,这个jQuery.Callbacks就到这里。需要注意的就是多个选项混合使用要特别小心。
如果觉得本文不错,请点击右下方【推荐】!
转载于:https://www.cnblogs.com/chuaWeb/p/jQuery-1-9-1-Callbacks.html
jQuery-1.9.1源码分析系列(五) 回调对象相关推荐
- 菜鸟读jQuery 2.0.3 源码分析系列(1)
原文链接在这里,作为一个菜鸟,我就一边读一边写 jQuery 2.0.3 源码分析系列 前面看着差不多了,看到下面一条(我是真菜鸟),推荐木有入门或者刚刚JS入门摸不着边的看看,大大们手下留情,想一起 ...
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
- [转]jQuery源码分析系列
文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...
- [转载]jQuery1.6.1源码分析系列
转载:http://www.cnblogs.com/nuysoft/archive/2011/11/14/2248023.html [原创] jQuery1.6.1源码分析系列(停止更新) 作者:nu ...
- MyBatis 源码分析系列文章合集
1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...
- MyBatis 源码分析系列文章导读
1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...
- Spring IOC 容器源码分析系列文章导读
1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...
- Spring IOC 容器源码分析系列文章导读 1
1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...
- dubbo源码分析系列(1)扩展机制的实现
1 系列目录 dubbo源码分析系列(1)扩展机制的实现 dubbo源码分析系列(2)服务的发布 dubbo源码分析系列(3)服务的引用 dubbo源码分析系列(4)dubbo通信设计 2 SPI扩展 ...
- idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(一)
课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...
最新文章
- 记录从数据库把数据初始化mongodb缓存的一些坑
- react 前端解析二进制流_一年半前端跳槽面试经验(头条、微信、shopee)
- dpkg: error processing package oracle-java8-installer (--configure):
- python中out什么意思_ref和out的使用与区别|python基础教程|python入门|python教程
- 自学python考哪些证书-Python自学难吗?有哪些课程内容?
- html禁用选择,html – 设置可选的禁用属性
- ads出现村田电容电感无法仿真的问题解决(`BJT1' is an instance of an undefined model `BJTM1')...
- FastDFS配置Nginx模块
- 使用基于轮询的SQL数据缓存依赖
- 声音均衡器怎么调好听_调音师必备:如何调出最佳人声?
- 校园网路由器——校园网禁止使用无线路由器或者第三方代理软件共享网络解决方案(breed web控制台+老毛子(Padavan)+锐捷(Ruijie)认证+电信闪讯(NetKeeper)L2TP学校)
- Mac Spotlight搜索快捷键
- DEM: 诊断事件管理 (Diagnostic Event Manager)
- 基于swiftype应用于Hexo-Yilia-主题的站内搜索引擎
- vue中设置显示默认图片
- 拼多多市值再次超越京东:进攻才是最好的防守!
- AS3中对声音的控制
- 在服务器端运行JavaScript文件(一)
- 微信扫码提示在浏览器中打开的遮罩代码解决方式
- Java开发设计模式