zoukankan      html  css  js  c++  java
  • jQuery 2.0.3 源码分析 回调对象

    jQuery.Callbacks()是在版本1.7中新加入的。它是一个多用途的回调函数列表对象,提供了一种强大的方法来管理回调函数队列。

    那么jQuery.Callbacks使用场景在哪里?

    在很多时候需要控制一系列的函数顺序执行。那么一般就需要一个队列函数来处理这个问题

    我们看一段代码

    复制代码
    function Aaron(List, callback) {
        setTimeout(function() {
          var task = List.shift();
          task(); //执行函数
          if (task.length > 0) {  //递归分解
            setTimeout(arguments.callee, 1000)
          } else {
            callback()
          }
        }, 25)
      }
    
      Aaron([function(){
        alert('a')
      },function(){
        alert('b')
      }],function(){
        alert('callback')
      })
     
    分别弹出 ‘a’ , ‘b’ ,’callback’
    复制代码

    传入一组函数参数,靠递归解析,分个执行,其实就是靠setTimeout可以把函数加入到队列末尾才执行的原理

    *****但是这样写,是不是很麻烦?*****

    我们换成jQuery提供的方式

    复制代码
    var callbacks = $.Callbacks();
    
      callbacks.add(function() {
        alert('a');
      })
    
      callbacks.add(function() {
        alert('b');
      })
    
      callbacks.fire(); //输出结果: 'a' 'b'
    复制代码

    是不是便捷很多了,代码又很清晰,所以它是一个多用途的回调函数列表对象,提供了一种强大的方法来管理回调函数队列。

    同时还提供几个便捷的处理参数

    • once: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred).
    • memory: 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred).
    • unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调).
    • stopOnFalse: 当一个回调返回false 时中断调用
    复制代码
    var callbacks = $.Callbacks('once');
    
      callbacks.add(function() {
        alert('a');
      })
    
      callbacks.add(function() {
        alert('b');
      })
    
      callbacks.fire(); //输出结果: 'a' 'b'
      callbacks.fire(); //未执行
    复制代码

    once的作用是使callback队列只执行一次

    OK,我们大概知道这个是干嘛用的了,可以开始上正菜了。


    .CallbacksjQuery使.ajax,$.Deferred等组件提供基础功能的函数,jQuery在1.5引入了Deferred对象(异步列队),jQuery内部基本所有有异步的代码都被promise所转化成同步代码执行了,后期在讨论了

    根据jQuery.Callbacks()的API

    提供一下几种方法:

    复制代码
    callbacks.add()        回调列表中添加一个回调或回调的集合。
    callbacks.disable()    禁用回调列表中的回调
    callbacks.disabled()   确定回调列表是否已被禁用。 
    callbacks.empty()      从列表中删除所有的回调.
    callbacks.fire()       用给定的参数调用所有的回调
    callbacks.fired()      访问给定的上下文和参数列表中的所有回调。 
    callbacks.fireWith()   访问给定的上下文和参数列表中的所有回调。
    callbacks.has()        确定列表中是否提供一个回调
    callbacks.lock()       锁定当前状态的回调列表。
    callbacks.locked()     确定回调列表是否已被锁定。
    callbacks.remove()     从回调列表中的删除一个回调或回调集合。
    复制代码

    我们看官网提供的demo

    复制代码
    function fn1( value ) {
        console.log( value );
    }
     
    function fn2( value ) {
        fn1("fn2 says: " + value);
        return false;
    }
    复制代码

    可以将上述两个方法作为回调函数,并添加到 $.Callbacks 列表中,并按下面的顺序调用它们:

    复制代码
    var callbacks = $.Callbacks();
    callbacks.add( fn1 );
     
    // outputs: foo!
    callbacks.fire( "foo!" );
     
    callbacks.add( fn2 );
     
    // outputs: bar!, fn2 says: bar!
    callbacks.fire( "bar!" );
    复制代码

    这样做的结果是,当构造复杂的回调函数列表时,将会变更很简单。可以根据需要,很方面的就可以向这些回调函数中传入所需的参数。

    上面的例子中,我们使用了 $.Callbacks() 的两个方法: .add() 和 .fire()。 .add() 可以向回调函数列表中添加新的回调函数,fire() 可以向回调函数中传递参数,并执行回调函数。

    设计思想:

    先看官网的demo这个列子,涉及到了 add 与 fire方法,熟悉设计模式的童鞋呢,一眼就看出,其实又是基于发布订阅的观察者模式的设计了

    pub/sub (观察者模式) 的背后,总的想法是在应用程序中增强松耦合性。并非是在其它对象的方法上的单个对象调用。一个对象作为特定任务或是另一对象的活动的观察者,并且在这个任务或活动发生时,通知观察者。观察者也被叫作订阅者(Subscriber),它指向被观察的对象,既被观察者(Publisher 或 subject)。当事件发生时,被观察者(Publisher)就会通知观察者(subscriber)

    作为 $.Callbacks() 的创建组件的一个演示,只使用回调函数列表,就可以实现 Pub/Sub 系统。将 $.Callbacks 作为一个队列

    我来模拟下常规最简单的实现

    复制代码
    var Observable = {
          callbacks: [],
          add: function(fn) {
            this.callbacks.push(fn);
          },
          fire: function() {
            this.callbacks.forEach(function(fn) {
              fn();
            })
          }
      }
    复制代码
    复制代码

    Observable.add(function() {
      alert(1)
    })
    Observable.fire(function() {
      alert(2)
    })

    Observable.fire(); // 1, 2

    复制代码

    构建一个存放回调的数组,如this.callbacks= [] 添加回调时,将回调push进this.callbacks,执行则遍历this.callbacks执行回调。

    也弹出1跟2了,实际上jQuery.callbacks是如何处理的呢?


    我们看源码
    整个$.Callbacks的源码很少,它是一个工厂函数,使用函数调用(非new,它不是一个类)创建对象,它有一个可选参数flags用来设置回调函数的行为。、

    对外的接口也就是self的返回

    image

    self上的add源码

    展开
    源码
    
    

    其中有一段代码要单独拿出来

    复制代码
    //这里用了一个立即执行的add函数来添加回调
    //直接遍历传过来的arguments进行push
    (function add( args ) {
        jQuery.each( args, function( _, arg ) {
            var type = jQuery.type( arg );
            //如果所传参数为函数,则push
            if ( type === "function" ) {
                if ( !options.unique || !self.has( arg ) ) {  //当$.Callbacks('unique')时,保证列表里面不会出现重复的回调
                    list.push( arg );
                }
            } else if ( arg && arg.length && type !== "string" ) {  //假如传过来的参数为数组或array-like,则继续调用添加,从这里可以看出add的传参可以有add(fn),add([fn1,fn2]),add(fn1,fn2)
                // Inspect recursively
                add( arg );
            }
        });
    })( arguments )
    复制代码
     

    add方法

    实参可以是Function, Array

    callbacks.add( callbacks )
    • callbacks

      类型: Function, Array

      一个函数,或者一个函数数组,用来添加到回调列表。

    如果是数组会递归调用私有的add函数 list.push( arg );

    发现没,设计的原理上其实跟上面发的简单模式 大同小异

    fire方法

    外观模式 self.fire –> self.fireWith –> fire

    最终执行代码是内部私有的fire方法了

    fire方法

    最终处理的代码

    list[ firingIndex ].apply( data[ 0 ], data[ 1 ] )

    其实就是拿出list中保存的回调函数,执行罢了,所以整个设计的原理,还是符合我们开始设想的


    具体的实现

    $.Callbacks( "once" )

    确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred).

    复制代码
    var callbacks = $.Callbacks( "once" );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );  //foo 只执行了一次,后面没执行了
    callbacks.add( fn2 );
    callbacks.fire( "bar" );
    callbacks.remove( fn2 );
    callbacks.fire( "foobar" ); 
    复制代码

    在fire中调用了 self.disable(); 方法

    // 禁用回调列表中的回调。
    disable: function() {
        list = stack = memory = undefined;
        return this;
    },

    $.Callbacks( "memory" )

    保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred).

    复制代码
    var callbacks = $.Callbacks("memory");
    
      callbacks.add(function() {
        console.log("f1");
      });
    
      callbacks.fire(); //输出 "f1",这时函数列表已经执行完毕!
    
      callbacks.add(function() {
        console.log("f2");
      }); //memory作用在这里,没有fire,一样有结果: f2
    复制代码

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


    $.Callbacks( "unique" )

    确保一次只能添加一个回调(所以在列表中没有重复的回调)

    复制代码
    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
    复制代码

    ****注意add方法默认不去重,比如这里fn1添加两次,fire时会触发两次****

    这里处理很简单

    if ( !options.unique || !self.has( arg ) ) { //确保是否可以重复
        list.push( arg );
    }                }

    在添加的到处理队列时候,判断一下即可


    $.Callbacks( "stopOnFalse" ):

    当一个回调返回false 时中断调用

    复制代码
    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
    复制代码

    附源码:

    复制代码
    jQuery.Callbacks = function( options ) {
    
        // Convert options from String-formatted to Object-formatted if needed
        // (we check in cache first)
        //通过字符串在optionsCache寻找有没有相应缓存,如果没有则创建一个,有则引用
        //如果是对象则通过jQuery.extend深复制后赋给options。
        options = typeof options === "string" ?
            ( optionsCache[ options ] || createOptions( options ) ) :
            jQuery.extend( {}, options );
    
        var // Last fire value (for non-forgettable lists)
            memory, // 最后一次触发回调时传的参数
    
            // Flag to know if list was already fired
            fired, // 列表中的函数是否已经回调至少一次
    
            // Flag to know if list is currently firing
            firing,  // 列表中的函数是否正在回调中
    
            // First callback to fire (used internally by add and fireWith)
            firingStart, // 回调的起点
    
            // End of the loop when firing
            firingLength, // 回调时的循环结尾
    
            // Index of currently firing callback (modified by remove if needed)
            firingIndex, // 当前正在回调的函数索引
    
            // Actual callback list
            list = [], // 回调函数列表
    
            // Stack of fire calls for repeatable lists
            stack = !options.once && [],// 可重复的回调函数堆栈,用于控制触发回调时的参数列表
    
            // Fire callbacks// 触发回调函数列表
            fire = function( data ) {
                //如果参数memory为true,则记录data
                memory = options.memory && data;
                fired = true; //标记触发回调
                firingIndex = firingStart || 0;
                firingStart = 0;
                firingLength = list.length;
                //标记正在触发回调
                firing = true;
                for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                    if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                        // 阻止未来可能由于add所产生的回调
                        memory = false; // To prevent further calls using add
                        break; //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环
                    }
                }
                //标记回调结束
                firing = false;
                if ( list ) {
                    if ( stack ) {
                        if ( stack.length ) {
                            //从堆栈头部取出,递归fire
                            fire( stack.shift() );
                        }
                    } else if ( memory ) {//否则,如果有记忆
                        list = [];
                    } else {//再否则阻止回调列表中的回调
                        self.disable();
                    }
                }
            },
            // Actual Callbacks object
            // 暴露在外的Callbacks对象,对外接口
            self = {
                // Add a callback or a collection of callbacks to the list
                add: function() { // 回调列表中添加一个回调或回调的集合。
                    if ( list ) {
                        // First, we save the current length
                        //首先我们存储当前列表长度
                        var start = list.length;
                        (function add( args ) { //jQuery.each,对args传进来的列表的每一个对象执行操作
                            jQuery.each( args, function( _, arg ) {
                                var type = jQuery.type( arg );
                                if ( type === "function" ) {
                                    if ( !options.unique || !self.has( arg ) ) { //确保是否可以重复
                                        list.push( arg );
                                    }
                                //如果是类数组或对象,递归
                                } else if ( arg && arg.length && type !== "string" ) {
                                    // Inspect recursively
                                    add( arg );
                                }
                            });
                        })( arguments );
                        // Do we need to add the callbacks to the
                        // current firing batch?
                        // 如果回调列表中的回调正在执行时,其中的一个回调函数执行了Callbacks.add操作
                        // 上句话可以简称:如果在执行Callbacks.add操作的状态为firing时
                        // 那么需要更新firingLength值
                        if ( firing ) {
                            firingLength = list.length;
                        // With memory, if we're not firing then
                        // we should call right away
                        } else if ( memory ) {
                            //如果options.memory为true,则将memory做为参数,应用最近增加的回调函数
                            firingStart = start;
                            fire( memory );
                        }
                    }
                    return this;
                },
                // Remove a callback from the list
                // 从函数列表中删除函数(集)
                remove: function() {
                    if ( list ) {
                        jQuery.each( arguments, function( _, arg ) {
                            var index;
                            // while循环的意义在于借助于强大的jQuery.inArray删除函数列表中相同的函数引用(没有设置unique的情况)
                            // jQuery.inArray将每次返回查找到的元素的index作为自己的第三个参数继续进行查找,直到函数列表的尽头
                            // splice删除数组元素,修改数组的结构
                            while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                list.splice( index, 1 );
                                // Handle firing indexes
                                // 在函数列表处于firing状态时,最主要的就是维护firingLength和firgingIndex这两个值
                                // 保证fire时函数列表中的函数能够被正确执行(fire中的for循环需要这两个值
                                if ( firing ) {
                                    if ( index <= firingLength ) {
                                        firingLength--;
                                    }
                                    if ( index <= firingIndex ) {
                                        firingIndex--;
                                    }
                                }
                            }
                        });
                    }
                    return this;
                },
                // Check if a given callback is in the list.
                // If no argument is given, return whether or not list has callbacks attached
                // 回调函数是否在列表中.
                has: function( fn ) {
                    return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
                },
                // Remove all callbacks from the list
                // 从列表中删除所有回调函数
                empty: function() {
                    list = [];
                    firingLength = 0;
                    return this;
                },
                // Have the list do nothing anymore
                // 禁用回调列表中的回调。
                disable: function() {
                    list = stack = memory = undefined;
                    return this;
                },
                // Is it disabled?
                //  列表中否被禁用
                disabled: function() {
                    return !list;
                },
                // Lock the list in its current state
                // 锁定列表
                lock: function() {
                    stack = undefined;
                    if ( !memory ) {
                        self.disable();
                    }
                    return this;
                },
                // Is it locked?
                // 列表是否被锁
                locked: function() {
                    return !stack;
                },
                // Call all callbacks with the given context and arguments
                // 以给定的上下文和参数调用所有回调函数
                fireWith: function( context, args ) {
                    if ( list && ( !fired || stack ) ) {
                        args = args || [];
                        args = [ context, args.slice ? args.slice() : args ];
                        //如果正在回调
                        if ( firing ) {
                            //将参数推入堆栈,等待当前回调结束再调用
                            stack.push( args );
                        } else {//否则直接调用
                            fire( args );
                        }
                    }
                    return this;
                },
                // Call all the callbacks with the given arguments
                // 以给定的参数调用所有回调函数
                fire: function() {
                    self.fireWith( this, arguments );
                    return this;
                },
                // To know if the callbacks have already been called at least once
                // // 回调函数列表是否至少被调用一次
                fired: function() {
                    return !!fired;
                }
            };
        return self;
    };
  • 相关阅读:
    python读写操作(txt, mat, xls, etc文件)
    开发linux版QQ就是支持未来的国产操作系统
    为知笔记linux绿色版的快速调用
    数学物理中的常见误区
    markdown语法小结
    信息爆炸时代的知识获取
    matlab: 数据的读写
    APS期刊投稿准备: REVTex格式
    markdown基本语法
    常见的数学关系
  • 原文地址:https://www.cnblogs.com/ymj0906/p/4213509.html
Copyright © 2011-2022 走看看