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;
}

  

d. fire函数详解


  该函数执行回调,最终执行代码段为

if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
memory = false; // 阻止未来可能由于add所产生的回调
break;
}
fire = function( data ) {
  //有memory才给memory赋值当前场景data
  memory = 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();
    }
  }

  首先看最外层的判断

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就到这里。需要注意的就是多个选项混合使用要特别小心。

  如果觉得本文不错,请点击右下方【推荐】!

jQuery-1.9.1源码分析系列(五) 回调对象的更多相关文章

  1. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  2. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  3. jQuery源码分析系列(转载来源Aaron.)

    声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...

  4. jQuery源码分析系列——来自Aaron

    jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...

  5. jquery2源码分析系列

    学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...

  6. jQuery-1.9.1源码分析系列完毕目录整理

    jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...

  7. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  8. MyCat源码分析系列之——SQL下发

    更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...

  9. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  10. MyCat源码分析系列之——前后端验证

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat前端验证 MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -pr ...

随机推荐

  1. asp.net 生成图形验证码(字母和数字混合)

    验证码技术是网站开发过程中比较重要的技术,可以防止非法人员利用注册机或者登陆工具来攻击我们的网站.下面是效果图: 具体实现方法如下: 1.主要思路是:引用Using System.Drawing命名空 ...

  2. Chrome必备的扩展

    Devtools Terminal ——浏览器终端.牛逼的不得了! LiveReload——为官方 LiveReload 应用程序(Mac 和 Windows)和第三方,例如 guard-livere ...

  3. nodejs事件轮询详述

    目录 概述 nodejs特点 事件轮询 关于异步方法 概述 关于nodejs的介绍网上资料非常多,最近由于在整理一些函数式编程的资料时,多次遇到nodejs有关的内容.所以就打算专门写一篇文章总结一下 ...

  4. 扩展Bootstrap Tooltip插件使其可交互

    最近在公司某项目开发中遇见一特殊需求,请笔者帮助,因此有了本文的插件.在前端开发中tooltip是一个极其常用的插件,它能更好向使用者展示更多的文档等帮助信息.它们通常都是一些静态文本信息.但同事他们 ...

  5. Windows+GCC下内存对齐的常见问题

    结构/类对齐的声明方式 gcc和windows对于modifier/attribute的支持其实是差不多的.比如在gcc的例子中,内存对齐要写成: class X { //... } __attrib ...

  6. ASP.NET MVC 路由(一)

    ASP.NET MVC路由(一) 前言 从这一章开始,我们即将进入MVC的世界,在学习MVC的过程中在网上搜索了一下,资料还是蛮多的,只不过对于我这样的初学者来看还是有点难度,自己就想看到有一篇引导性 ...

  7. 可扩容分布式session方案

    分布式session有以下几种方案: 1. 基于nfs(net filesystem)的session共享 将共享服务器目录mount各服务器的本地session目录,session读写受共享服务器i ...

  8. TDR测试原理

    什么是TDR? TDR是英文Time Domain Reflectometry 的缩写,中文名叫时域反射计,是测量传输线特性阻抗的主要工具.TDR主要由三部分构成:快沿信号发生器,采样示波器和探头系统 ...

  9. Objective-C 工厂模式(下) -- 抽象工厂模式

    相比简单工厂模式, 只有一个工厂 能生产的手机也是固定的 抽象工厂模式类似于有很多家工厂, 当用户要买什么手机就创建对应的工厂去生产 比如用户要买iPhone就创建一个Apple工厂来生产手机, 要买 ...

  10. iOS 离屏渲染的研究

    GPU渲染机制: CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示. G ...