zoukankan      html  css  js  c++  java
  • jQuery.Callbacks源码解读

    序言:

    最近在学习jQuery.Callbacks对象,看jQuery.Callbacks的API文档,不是很懂,因此看看其源码部分,理解其使用方法,记录下自己在阅读源码时的记录并分享给大家。

    有理解不透的地方望同仁指点,代码来源:jQuery 1.9.1版本。作者:华子yjh,欢迎转载,转载时请注明出处并附上原文链接

    一、源码解读

    /*
     * Create a callback list using the following parameters:
     *
     *    options: an optional list of space-separated options that will change how
     *            the callback list behaves or a more traditional option object
     *
     * By default a callback list will act like an event callback list and can be
     * "fired" multiple times.
     *
     * Possible options:
     *
     *    once:          确保回调列表仅只fire一次
                         will ensure the callback list can only be fired once (like a Deferred)
     *
     *    memory:        在执行过fire后,保存之前fire时的参数,该参数会传递给在add中执行的最新添加的回调
                         will keep track of previous values and will call any callback added
     *                    after the list has been fired right away with the latest "memorized"
     *                    values (like a Deferred)
     *
     *    unique:        确保在add操作中,阻止存在回调列表中的回调再次被添加到回调列表中
                         will ensure a callback can only be added once (no duplicate in the list)
     *
     *    stopOnFalse:   当正在执行的回调返回false,将中断其他未执行回调的执行
                         interrupt callings when a callback returns false
     *
     */
    
    var optionsCache = {},
        // Used for splitting on whitespace
        core_rnotwhite = /\S+/g;
    
    // Convert String-formatted options into Object-formatted ones and store in cache
    function createOptions( options ) {
        // optionsCache[ options ] 用于缓存 object所引用的值
        var object = optionsCache[ options ] = {};
        jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
            object[ flag ] = true;
        });
        return object;
    }
    
    jQuery.Callbacks = function( options ) {
    
        // Convert options from String-formatted to Object-formatted if needed
        // (we check in cache first)
        options = typeof options === "string" ?
            // 只有当执行$.Callbacks(参数相同)二次及以上时,才不会执行createOptions函数
            ( optionsCache[ options ] || createOptions( options ) ) :
            // 说明也可以这样$.Callbacks({once:true, memory:true})使用
            jQuery.extend( {}, options );
    
        var // Flag to know if list is currently firing
            firing,
            // Last fire value (for non-forgettable lists)
            memory,
            // Flag to know if list was already fired
            fired,
            // End of the loop when firing
            firingLength,
            // Index of currently firing callback (modified by remove if needed)
            firingIndex,
            // First callback to fire (used internally by add and fireWith)
            firingStart,
            // Actual callback list
            list = [],
            // Stack of fire calls for repeatable lists
            stack = !options.once && [],
            // Fire callbacks
            // data传递的是一个数组
            // 使用Callbacks.fireWidth时,data包含fireWith函数传递的一个上下文环境和一个数组
            // 使用Callbacks.fire时,data包含Callbacks对象和fire函数的arguments对象
            fire = function( data ) {
                // 如果options.memory为true,记录下data传递的数组
                memory = options.memory && data;
                fired = true;
                // 如果options.memory为true,firingStart为上一次Callbacks.add后回调列表的length值
                firingIndex = firingStart || 0;
                // 重置firingStart为0
                firingStart = 0;
                firingLength = list.length;
                firing = true;
                for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                    // 将data作为参数,执行回调列表中的所有回调
                    // 如果回调列表中其中一个回调返回false,且options.stopOnFalse为true,则中断接下来其他回调的执行
                    // 如果options.memory为true,将memory设置为false,阻止在Callbacks.add中新增回调的执行
                    if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                        memory = false; // To prevent further calls using add
                        break;
                    }
                }
                firing = false;
                if ( list ) {
                    // 如果options.once为false
                    if ( stack ) {
                        // stack在fireWith操作时可能拥有成员
                        // 当stack拥有成员时(只有一个),并将其成员作为参数传递给将要执行的所有回调
                        if ( stack.length ) {
                            fire( stack.shift() );
                        }
                    }
                    // 如果options.memory为true且options.once为true,那么在add中执行一次fire(只执行最新添加的回调)
                    // 这里设置list = [],那么再次fire时,将不会有任何操作
                    else if ( memory ) {
                        list = [];
                    }
                    // 如果存在options.once为true,options.memory为false的其他情况,将禁用Callbacks对象
                    else {
                        self.disable();
                    }
                }
            },
            // Actual Callbacks object
            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, function( _, arg ) {
                                var type = jQuery.type( arg );
                                // 如果arg是一个函数
                                if ( type === "function" ) {
                                    // arg为要新增到回调列表中的回调
                                    // 如果arg不存在回调列表中,则将它增加到回调列表中
                                    // 如果arg存在回调列表中,且options.unique为false,则将它增加到回调列表中
                                    // 如果arg存在回调列表中,且options.unique为true,则不执行push动作
                                    if ( !options.unique || !self.has( arg ) ) {
                                        list.push( arg );
                                    }
                                }
                                // 如果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
                        // 如果options.memory为true,则将memory做为参数,应用最近增加的回调函数
                        } else if ( memory ) {
                            firingStart = start;
                            fire( memory );
                        }
                    }
                    return this;
                },
                // Remove a callback from the list
                remove: function() {
                    if ( list ) {
                        jQuery.each( arguments, function( _, arg ) {
                            var index;
                            // 通过找到arguments成员在回调列表中索引位置遍历arguments对象,并将arguments成员从回调列表中移除
                            while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                list.splice( index, 1 );
                                // Handle firing indexes
                                // 如果在执行Callbacks.remove操作的状态为firing时
                                // 则更新firingLength和firingIndex的值
                                if ( firing ) {
                                    if ( index <= firingLength ) {
                                        firingLength--;
                                    }
                                    // 特殊处理,如果移除的回调的索引小于当前正在执行回调的索引,则firingIdex--
                                    // 后面未执行的回调则得以正常执行
                                    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.
                // 检查fn是否存在回调列表中
                has: function( fn ) {
                    return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
                },
                // Remove all callbacks from the list
                // 清空回调列表中的所有回调
                empty: function() {
                    list = [];
                    return this;
                },
                // Have the list do nothing anymore
                // 禁用Callbacks对象
                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 ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    if ( list && ( !fired || stack ) ) {
                        // 如果fired为true,options.once为false,且如果在执行fireWith操作的状态为firing
                        // 则将处理过的args作为stack数组的第一个数组项,并在firing为false后,将stack第一个数组项作为参数传递给将要执行的所有回调
                        
                        // 如果fired为true,options.once为true,则不执行任何操作
                        // 可以看出,$.Callbacks('once')表示回调对象只fire一次
                        if ( firing ) {
                            stack.push( args );
                        }
                        // 第一次执行fireWith时,一定执行else分支
                        else {
                            fire( args );
                        }
                    }
                    return this;
                },
                // Call all the callbacks with the given arguments
                // 将Callbacks对象,及Callbacks.fire函数中的arguments对象传递给Callbacks.fireWith函数,并执行
                fire: function() {
                    self.fireWith( this, arguments );
                    return this;
                },
                // To know if the callbacks have already been called at least once
                // 是否执行过fire函数
                fired: function() {
                    return !!fired;
                }
            };
        
        //返回Callbacks对象
        return self;
    };

    二、jQuery.Callbacks内部中firing为true的实例

    1、在Callbacks.add中firing为true的实例

    $(function(){
        // 定义三个将要增加到回调列表的回调函数fn1,fn2,fn3        
        function fn1(arg){
            console.log( 'fn1 says:' + arg );
            // 在fn1中执行Callbacks.add操作,此时Callbacks函数内部的firingLength将会得到更新
            $callbacks.add(fn2);
        }
        function fn2(arg){
            console.log( 'fn2 says:' + arg );
        }
        function fn3(arg){
            console.log( 'fn3 says:' + arg );
        }
        
        // Callbacks传递了memory
        // 也可以这样使用$.Callbacks({ memory: true });
        var $callbacks = $.Callbacks('memory');
        
        // 将fn1增加到回调列表中,因为在fn1中有执行了add(fn2)操作,因此回调列表中的回调为fn1,fn2
        $callbacks.add(fn1);
        
        // output: fn1 says:foo
        // output: fn2 says:foo
        $callbacks.fire('foo');
        
        // 将之前fire的参数传递给最近增加的回调fn3,并执行fn3
        // output: fn3 says:foo
        $callbacks.add(fn3);
        
        // 再执行一次fire,注意此时回调列表中的回调一次是fn1,fn2,fn3,fn2
        // output: fn1 says:baz
        // output: fn2 says:baz
        // output: fn3 says:baz
        // output: fn2 says:baz
        // 如果期望回调列表中只有fn1,fn2,fn3,只需在Callbacks函数中传入unique
        $callbacks.fire('baz');
    });

    2、在Callbacks.fireWith中firing为true的实例

    $(function(){    
        function fn1(arg){
            console.log( 'fn1 says:' + arg );
        }
        function fn2(arg){
            console.log( 'fn2 says:' + arg );
            $callbacks.fireWith(window, ['yjh']);
            
            // 一定要执行这一步,否则将会陷入死循环
            $callbacks.remove(fn2);
        }
        
        var $callbacks = $.Callbacks();
        $callbacks.add(fn1);
        
        // output: fn1 says:foo
        $callbacks.fire('foo');    
    
        $callbacks.add(fn2);    
        // output: fn1 says:baz
        // output: fn2 says:baz
        // output: fn1 says:yjh
        $callbacks.fire('baz');
    });
  • 相关阅读:
    如何去除电脑上软件图标的快捷键小箭头
    三维地图如何加载gltf数据代码
    实时获取三维地图相机角度,改变三维观赏角度
    完美解决win10家庭版本系统无法远程连接问题
    svn提交批量选中文件
    oracle sql developer 如何支持多个窗体,打开多张表,多个tab,同时查看多个数据表
    sqldevelper批量导出sql文件
    原生javascript与jquery的区别(持续记录)
    iframs里子,孙页面与父,爷页面,以及多层嵌套的iframe中,js变量,方法以及元素的互相获取,调用
    Eclipse如何将多行注释的星号去掉
  • 原文地址:https://www.cnblogs.com/yangjunhua/p/3014368.html
Copyright © 2011-2022 走看看