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 相关文章推荐
模拟一个类似百度google的模糊搜索下拉列表
Apr 15 Javascript
jquery 表格排序、实时搜索表格内容(附图)
May 19 Javascript
PHP守护进程实例
Mar 06 Javascript
js+html5实现canvas绘制简单矩形的方法
Jun 05 Javascript
详解javascript事件绑定使用方法
Oct 20 Javascript
JavaScript设计模式之单例模式详解
Jun 09 Javascript
Vue2 配置 Axios api 接口调用文件的方法
Nov 13 Javascript
web3.js增加eth.getRawTransactionByHash(txhash)方法步骤
Mar 15 Javascript
详解Vue2.0配置mint-ui踩过的那些坑
Apr 23 Javascript
简单学习5种处理Vue.js异常的方法
Jun 17 Javascript
JavaScript函数重载操作实例浅析
May 02 Javascript
JS原形与原型链深入详解
May 09 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 cli模式下获取参数的方法
2017/05/05 PHP
PHP面向对象程序设计重载(overloading)操作详解
2019/06/13 PHP
[原创]提供复制本站内容时出现,该文章转自脚本之家等字样的js代码
2007/03/27 Javascript
使用新的消息弹出框blackbirdjs
2008/10/16 Javascript
jQeury淡入淡出需要注意的问题
2010/09/08 Javascript
JavaScript 变量作用域分析
2011/07/04 Javascript
javascript中将Object转换为String函数代码 (json str)
2012/04/29 Javascript
javascript实现的DES加密示例
2013/10/30 Javascript
js实现网页随机切换背景图片的方法
2014/11/01 Javascript
jQuery插件之jQuery.Form.js用法实例分析(附demo示例源码)
2016/01/04 Javascript
JavaScript中的this机制
2016/01/30 Javascript
你一定会收藏的Nodejs代码片段
2016/02/04 NodeJs
jQuery购物网页经典制作案例
2016/08/19 Javascript
JS实现的ajax和同源策略(实例讲解)
2017/12/01 Javascript
js实时监控文本框输入字数的实例代码
2018/01/18 Javascript
vue 项目中使用Loading组件的示例代码
2018/08/31 Javascript
仿照Element-ui实现一个简易的$message方法
2020/09/14 Javascript
在Python下使用Txt2Html实现网页过滤代理的教程
2015/04/11 Python
Python可变参数函数用法实例
2015/07/07 Python
基于python脚本实现软件的注册功能(机器码+注册码机制)
2016/10/09 Python
浅谈终端直接执行py文件,不需要python命令
2017/01/23 Python
使用python和Django完成博客数据库的迁移方法
2018/01/05 Python
Python学习笔记之列表推导式实例分析
2019/08/13 Python
Python requests设置代理的方法步骤
2020/02/23 Python
python实现门限回归方式
2020/02/29 Python
浅谈Python 钉钉报警必备知识系统讲解
2020/08/17 Python
您的健身减肥和健康饮食专家:vitafy
2017/06/06 全球购物
.NET初级开发工程师面试题(包括Javascript)
2012/08/22 面试题
捐赠仪式主持词
2014/03/19 职场文书
教师教学评估方案
2014/05/09 职场文书
邀请函怎么写
2015/01/30 职场文书
听证通知书
2015/04/24 职场文书
在职证明范本
2015/06/15 职场文书
幼儿园毕业致辞
2015/07/29 职场文书
Vue和Flask通信的实现
2021/05/19 Vue.js
Redis 限流器
2022/05/15 Redis