支付宝赞助帐号:service@kuitao8.com 

jQuery.Callbacks() 回调函数队列 分析

Created2014-03-05   Views 3884    Author 懒人程序

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 模式,建立了程序间的松散耦合和高效通信。

文章参考:

http://zsuczw.iteye.com/blog/1121129

http://blog.csdn.net/yanyan19880509/article/details/7366013

上一篇: jQuery.event 事件机制 全解析(一)
下一篇: jQuery的deferred对象详解
支持键盘 ← →

邮件订阅

订阅我们的精彩内容