jQuery.Callbacks()回调函数队列用法详解


Posted in Javascript onJune 14, 2016

本文实例讲述了jQuery.Callbacks()回调函数队列用法。分享给大家供大家参考,具体如下:

1、jQuery.Callbacks

The jQuery.Callbacks() function, introduced in version 1.7, returns a multi-purpose object that provides a powerful way to manage callback lists. It supports adding, removing, firing, and disabling callbacks.

The $.Callbacks() function is internally used to provide the base functionality behind the jQuery $.ajax() and $.Deferred() components. It can be used as a similar base to define functionality for new components.

接下来,我们分别看下四个标准的控制标志。

1.1 once

创建的 callbacks 对象只允许被 fireWith() 一次 [注意:方法fire() 是 fireWith() 的外观模式]。

var callbacks = $.Callbacks("once");
callbacks.add(function(){console.log("f1");});
callbacks.fire(); //输出 "f1"
callbacks.fire(); //什么也不发生,在源码中已经禁用了 list.disable()

1.2 memory

在调用 add() 方法时,如果这时 callbacks队列 满足 fired && firing = false(真执行完毕) && memory(需要在构造函数指定),那么add() 进去的回调函数会立即执行,而这个 add 进去的回调函数调用时的参数存储在 memory 变量中。memory 变量用于存储最后一次调用 callbacks.fireWith(...) 时所使用的参数 [context, arguments]。

If the Callbacks object is created with the "memory" flag as its argument, additional functions may be added and fired after the callback list is locked.

$(function($){
    var callbacks = $.Callbacks("memory");
    callbacks.add(function(){console.log("f1");});
    callbacks.fire(); //输出 "f1",这时函数列表已经执行完毕!
    callbacks.add(function(){console.log("f2");});  //memory作用在这里,没有fire,一样有结果: f2
    callbacks.fire(); //重新触发一次,输出 f1 f2。 firingStart = 0
    //与once一起使用
    callbacks = $.Callbacks("once memory");
    callbacks.add(function(){console.log("f3");});
    callbacks.fire(); //输出 "f3",这时函数列表已经执行完毕!
    callbacks.add(function(){console.log("f4");});      //没有fire,一样有结果: f4
    callbacks.fire(); //由于为"once",这里将什么也不执行
});

1.3 unique

回调函数列表中的函数是否可以重复,该特性与 add() 方法有关,可以避免在回调函数列表中加入多个相同回调函数。

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

1.4 stopOnFalse

默认情况下,当执行 fireWith() 方法时,整个回调函数列表中的所有函数都会顺序执行,但如果设置了stopOnFalse,那么当某个函数返回false时,后边的函数将不再执行。即使设置了memory,再次添加的函数也不会执行了,即一旦某个函数返回 false 的情况下,会禁用 memory 功能。但如果没设置”once”,再次调用fire可以重新触发该callbacks。

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

2. memory 回调队列

var i = 0;
var inc = function (s){
 i++;
 alert(i +"$" + s);
};
var callbacks = $.Callbacks('memory');
callbacks.add(function iteral() {
 callbacks.add(inc);
 if (i <= 1) {
  callbacks.fire(i);
 }
});
callbacks.fire(i);
callbacks.add(inc);
/*
list = [];
list = [it];
--->fire(0), i=0
1、list = [it, inc]
2、push(fire(0))
3、i++ [inc(0)] (i=1)
shift()--->fire(0), i=1
1、list = [it, inc, inc];
2、push(fire(1)),
3、i++ [inc(0)]
4、i++ [inc(0)] (i=3)
shift()--->fire(1),i=3
1、list = [it, inc, inc, inc];
2、i++ [inc(1)]
3、i++ [inc(1)]
4、i++ [inc(1)] (i=6)
--->add(inc), i=6, memory=[this,1]
1、i++ [inc(1)] (i=7)
*/

3、 jQuery.CallBacks 源码

说明:为了便于理解,修改了部分源码,减少了一些功能~~~

jQuery.Callbacks = function (options) {
  // string --> object 改进建议:将未配置的参数缺省为false,而不是undefined。便于程序阅读和控制.
  options = optionsCache[options] || createOptions(options);
  var firing,
    memory, //Last fire value [context, args] (for memory lists)
    fired,
    firingLength,
    firingIndex,
    firingStart,
    list = [],
    stack = options.once === true ? false : [], // Stack of fire calls for repeatable lists
    fire = function (data) { // data --> [context, args]
      memory = !!options.memory && data; // false OR [context, arguments]
      fired = true;
      firingIndex = firingStart || 0;
      firingStart = 0;
      firingLength = list.length;
      firing = true;
      // 这里 list 放在条件判断中是因为执行回调函数可能会改变 list 的状态,比如 this.disable()。
      for ( ; list && firingIndex < firingLength; firingIndex++) {
        if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse === true) {
          memory = false; // 禁止 memory 功能,这样调用 add() 增加新回调函数不会立即自动调用
          break;
        }
      }
      firing = false;
      if (list) {
        if (stack) {
        //进入条件: fired && firing === false && stack, 实现递归调用
          if (stack.length) {
            fire(stack.shift()); // [[context1, arguments1], [context2, arguments2]]
          }
        } else if (memory) {
        // 进入条件: fired && firing === false && stack === undefined && 有memory字段(memory变量只能通过fire()函数修改)
        // 这里的 list = [],主要是用于性能优化,以防该对象长时间不执行,占用系统内存
          list = [];
        } else {
        // 进入条件: fired && firing === false && stack === undefined && 没有memory字段, 说明必要继续保留的必要
          self.disable();
        }
      }
    },
    self = {
      add: function() {
        if (list) {                  //几乎所有API都应该绑定这个条件,因为我们需要处理队列
          var originLength = list.length;
          jQuery.each(arguments, function( _, arg) {
            if (jQuery.type(arg) === "function") {
                // (!(options.unique && self.has(arg))) unique字段的作用
                if (!options.unique || !self.has(arg)) {
                  list.push(arg);
                }
            }
          });
          if (firing === true) {
          // 进入条件: 说明正在执行回调函数队列中,而当前执行的这个回调函数激活了add()函数,及时维护循环边界
            firingLength = list.length;
          } else if (memory) {
          // 进入条件: memory && fired && firing === false, 说明之前的 fire() 行为已经完全结束
            firingStart = originLength;
            fire(memory);
          }
        }
        return this;
      },
      remove: function() {
        if (list) {
          jQuery.each(arguments, function( _, arg) {
            var lastIndex;
            while ((lastIndex = jQuery.inArray(arg, list, lastIndex)) >= 0) {
              list.splice(lastIndex, 1);
              if (firing === true) {         // 及时更新边界条件,实现智能处理
                if (lastIndex <= firingLength) {
                  firingLength--;
                }
                if (lastIndex <= firingIndex) {
                  firingIndex--;
                }
              }
            }
          });
        }
        return this;
      },
      has: function (func) { //这个API有两个功能,根据单一职责角度来说,应该增加一个 isNotEmpty() 接口(非空)
        return func ? jQuery.inArray(func, list) > -1 : !!(list && list.length);
      },
      empty: function() {
        list = [];
        return this;
      },
      disable: function() { // 彻底禁用该对象, stack禁用, memory禁用
        list = stack = memory = undefined;
        return this;
      },
      disabled: function() {
        return !list;
      },
      lock: function() {
        stack = undefined;
        // 如果memory没有存储调用状态,直接禁用这个对象(可能是从未调用就被锁定,或者没有memory字段)
        if (!memory) {
          self.disable();
        }
        return this;
      },
      locked: function() {
        return !stack;
      },
      fireWith: function (context, args) {
        args = args || [];
        var data = [context, args];
        if (list && (fired === false || stack) ) {
          if (firing) {
     // 进入条件:  firing === true && stack  说明当前正在执行回调函数队列
            stack.push(data);      // stack其实是一个队列结构,这里用 stack 有些混淆
          } else {
     // 进入条件一: firing === false && fired === false        说明从来没有 fire()过
     // 进入条件二: firing === false && fired === true && stack = [] 说明至少调用过一次,而且当前允许多次调用,可以通过lock()锁定
            fire(args);
          }
        }
        return this;
      },
      fire: function() {
        self.fireWith(this, arguments);
        return this;
      },
      fired: function() {
        return !!fired;
      }
    };
  return self;
};

4、胡思乱想

jQuery.Callbacks() 方法的核心是 fire() 方法,将该 fire() 方法被封装在函数中不可直接访问,因此像 memory、firing、fired 这些状态对于外部上下文来说是不可更改的。

还有需要注意的是,如果回调函数中使用了 this 对象,可以直接用这个 this 来访问self对象的公有API。当然,也可以用 fireWith() 自己指定 this 的引用对象。

jQuery.Callbacks()的核心思想是 Pub/Sub 模式,建立了程序间的松散耦合和高效通信。

希望本文所述对大家jQuery程序设计有所帮助。

Javascript 相关文章推荐
让jQuery Mobile不显示讨厌loading界面的方法
Feb 19 Javascript
两种不同的方法实现js对checkbox进行全选和反选
May 13 Javascript
javascript中Date对象的getDay方法使用指南
Dec 22 Javascript
javascript创建含数字字母的随机字符串方法总结
Aug 01 Javascript
AngularJS中transclude用法详解
Nov 03 Javascript
AngularJs验证重复密码的方法(两种)
Nov 25 Javascript
echarts设置图例颜色和地图底色的方法实例
Aug 01 Javascript
node.js的Express服务器基本使用教程
Jan 09 Javascript
详解如何在Vue项目中导出Excel
Apr 19 Javascript
解决layer弹出层中表单不起作用的问题
Sep 09 Javascript
详解mpvue开发微信小程序基础知识
Sep 23 Javascript
React+EggJs实现断点续传的示例代码
Jul 07 Javascript
基于gulp合并压缩Seajs模块的方式说明
Jun 14 #Javascript
JS去除空格和换行的正则表达式(推荐)
Jun 14 #Javascript
javascript用正则表达式过滤空格的实现代码
Jun 14 #Javascript
三种带箭头提示框总结实例
Jun 14 #Javascript
js判断输入字符串是否为空、空格、null的方法总结
Jun 14 #Javascript
简单实现的JQuery文本框水印插件
Jun 14 #Javascript
JS不用正则验证输入的字符串是否为空(包含空格)的实现代码
Jun 14 #Javascript
You might like
PHP实现通过中文字符比率来判断垃圾评论的方法
2014/10/20 PHP
php之curl设置超时实例
2014/11/03 PHP
PHP中strcmp()和strcasecmp()函数字符串比较用法分析
2016/01/07 PHP
PHP PDOStatement::bindValue讲解
2019/01/30 PHP
PHP从零开始打造自己的MVC框架之入口文件实现方法详解
2019/06/03 PHP
JS获取父节点方法
2009/08/20 Javascript
javascript完美拖拽的实现方法
2013/09/29 Javascript
JavaScript中的常见问题解决方法(乱码,IE缓存,代理)
2013/11/28 Javascript
javascript 模拟坦克大战游戏(html5版)附源码下载
2014/04/08 Javascript
点击A元素触发B元素的事件在IE8下会识别成A元素
2014/09/04 Javascript
浅谈JSON.parse()和JSON.stringify()
2015/07/14 Javascript
Javascript简单改变表单元素背景的方法
2015/07/15 Javascript
JS基于构造函数实现的菜单滑动显隐效果【测试可用】
2016/06/21 Javascript
浅谈jquery的html方法里包含特殊字符的处理
2016/11/30 Javascript
JS正则表达式判断有效数实例代码
2017/03/13 Javascript
jQuery Validate 无法验证 chosen-select元素的解决方法
2017/05/17 jQuery
jQuery实现表单动态加减、ajax表单提交功能
2018/06/08 jQuery
JavaScript中的回调函数实例讲解
2019/01/27 Javascript
Vue 子组件与数据传递问题及注意事项
2019/07/11 Javascript
一文秒懂nodejs中的异步编程
2021/01/28 NodeJs
Anaconda入门使用总结
2018/04/05 Python
Pycharm 操作Django Model的简单运用方法
2018/05/23 Python
pandas.DataFrame选取/排除特定行的方法
2018/07/03 Python
Python SELENIUM上传文件或图片实现过程
2019/10/28 Python
Python通过Tesseract库实现文字识别
2020/03/05 Python
python小技巧——将变量保存在本地及读取
2020/11/13 Python
matplotlib 范围选区(SpanSelector)的使用
2021/02/24 Python
详解HTML5 录音的踩坑之旅
2017/12/26 HTML / CSS
html5移动端自适应布局的实现
2020/04/15 HTML / CSS
美国职棒大联盟的官方手套、球和头盔:Rawlings
2020/02/15 全球购物
Linux面试经常问的文件系统操作命令
2015/11/05 面试题
shell变量的作用空间是什么
2013/08/17 面试题
大专生的学习自我评价
2013/12/04 职场文书
会计求职简历自我评价
2015/03/10 职场文书
2015年出纳年终工作总结
2015/05/14 职场文书
2016年端午节校园广播稿
2015/12/18 职场文书