• jQuery.Callbacks 源码解读二


    一、参数标记

    /*
     * once: 确保回调列表仅只fire一次
     * unique: 在执行add操作中,确保回调列表中不存在重复的回调
     * stopOnFalse: 当执行回调返回值为false,则终止回调队列的执行
     * momery: 记录上一次fire时的参数,并在add中传递给fire和执行fire,执行时firingIndex为上一次fire时的firingLength
     */

    二、源码解读分析

    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 ) {
        // 多个变量指向同一对象(或数组)引用时,其中一个变量修改了被引用对象的内部结构,其他引用变量也会表现出来
        var object = optionsCache[ options ] = {};
        jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
            object[ flag ] = true; // optionsCache[ options ][ 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" ?
            // 缓存所有的参数标志,当再次传递已传递过的参数标志,则使用缓存值optionsCache[ options ]        
            ( 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为fireWith内部整理的args数组
            fire = function( data ) {
                memory = options.memory && data;
                fired = true;
    
                // 处理在add中,options.memory = true;的情况
                firingIndex = firingStart || 0;
                firingStart = 0;
    
                firingLength = list.length;
                firing = true;
                for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                    // 正在执行的回调返回值为false 且 options.stopOnFalse为true,则终止回调队列的执行
                    if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                        memory = false; // To prevent further calls using add
                        break;
                    }
                }
                firing = false;
                if ( list ) {
                    // 处理正在执行的回调中执行fireWith的操作;
                    if ( stack ) {
                        if ( stack.length ) {
                            fire( stack.shift() );
                        }
                    }
                    // 上一分支状态为回调执行过,且可以执行多次
                    // 此时 options.once = true; 这里将list设置为[],只是确保下次执行fire时,无回调执行
                    // 但是如果 options.memory = true; 仍然会执行add中的fire操作,因为此时回调列表中已有回调
                    else if ( memory ) {
                        list = [];
                    }
                    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 );
                                if ( type === "function" ) {
                                    // 回调不唯一 或 唯一且不存在,则push
                                    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?
                        // 正在执行的回调执行了add操作,则更新firingLength
                        if ( firing ) {
                            firingLength = list.length;
    
                        // With memory, if we're not firing then
                        // we should call right away
                        // 如果options.memory为true,则再次执行fire,且参数相同,fire中的firingIndex为此时的firingStart
                        }
                        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;                
                            while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                // 查找到所对应的索引,则移除索引项
                                list.splice( index, 1 );
    
                                // Handle firing indexes
                                // 正在执行的回调执行了remvoe操作,则更新firingLength和firingIndex的值
                                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 = [];
                    return this;
                },
                // Have the list do nothing anymore
                // 禁用add,remove,fire主要方法的工作
                disable: function() {
                    list = stack = memory = undefined;
                    return this;
                },
                // Is it disabled?
                disabled: function() {
                    return !list;
                },
                // Lock the list in its current state
                lock: function() {
                    // 如果回调执行过,则将阻止self.fire操作
                    // 但如果 options.memory = true,则仍然会执行fire操作
                    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 ) ) {
    
                        // 正在执行的回调函数执行了fireWith操作( 暗指回调列表已执行过,且可以执行多次,stack = []; )
                        // 该函数需要条件执行,或有移除该函数的操作,否则陷入死循环,详见例2
                        if ( firing ) {
                            stack.push( args );
                        }
                        // 正在执行的回调函数没有执行fireWith操作
                        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;
    };

    三、示例

    例1:

    $(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');
    });
    View Code

    例2

    $(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');
    });
    View Code

    PS:

    此前写过一篇关于jQuery.Callbacks源码分析的随笔,理解不透彻,今天又重新翻阅了一下,记录一下自己的源码阅读,相比之前,感觉好多了。

    阅读前,可以先看API,弄清楚四个参数标志,'once', 'memory', 'unique', 'stopOnFalse', 简单的执行add, fire操作,然后再看源码;

    阅读顺序:

    1、先阅读var声明的变量,fire函数的前半部分,self对象中的add, remove函数,有些难以理解暂时往下看;

    2、然后阅读self对象中的fire,fireWith,最后再来阅读fire函数,弄清楚后再看其他self对象中的方法。

    转载请注明出处:博客园华子yjh

  • 相关阅读:
    Python 冒泡排序
    编程规范之注释篇
    编程规范之变量命名篇
    安装Django
    字典&列表的拓展理解
    小球落地
    Python结合sql登陆案例
    面向对象:两手交换牌
    每日一题(一)
    Web应用之LAMP源码环境部署
  • 原文地址:https://www.cnblogs.com/yangjunhua/p/3381258.html
Copyright © 2020-2023  润新知