jQuery源码 Callbacks

作者: 柠檬果然酸 | 来源:发表于2020-04-15 22:42 被阅读0次

    官方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() .add() 支持添加新的回调列表, 而.fire() 提供了一种用于处理在同一列表中的回调方法的途径。

    另一种方法是$.Callbacks 的.remove()方法,用于从回调列表中删除一个特定的回调。下面是.remove()使用的一个例子

    var callbacks = $.Callbacks();
    callbacks.add( fn1 );
    // outputs: foo!
    callbacks.fire( "foo!" );
    callbacks.add( fn2 );
    // outputs: bar!, fn2 says: bar!
    callbacks.fire( "bar!" );
    callbacks.remove( fn2 );
    // only outputs foobar, as fn2 has been removed.
    callbacks.fire( "foobar" );
    

    jQuery.Callbacks还提供“once memory”等参数用来处理
    1.once: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred)。
    2.memory: 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred)。
    3.unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调)。
    4.stopOnFalse: 当一个回调返回false 时中断调用。

    var callbacks = $.Callbacks('once');
    
    callbacks.add(function() {
        alert('a');
    })
    
    callbacks.add(function() {
        alert('b');
    })
    
    callbacks.fire(); //输出结果: 'a' 'b'
    callbacks.fire(); //未执行
    

    jQuery回调模块结构
    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()     :从回调列表中的删除一个回调或回调集合。
    

    源码结构

    jQuery.Callbacks = function(options) {
        options = typeof options === "string" ?
            (optionsCache[options] || createOptions(options)) :
            jQuery.extend({}, options);
        //实现代码
        fire = function() {}
        self = {
            add: function() {},
            remove: function() {},
            has: function(fn) {},
            empty: function() {},
            disable: function() {},
            disabled: function() {},
            lock: function() {},
            locked: function() {},
            fireWith: function(context, args) {},
            fire: function() {},
            fired: function() {}
        };
        return self;
    };
    
    // 非空格
    var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
    
    // Convert String-formatted options into Object-formatted ones
    function createOptions( options ) {
        var object = {};
        jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
            object[ flag ] = true;
        } );
        return object;
    }
    

    源码

    // 回调模块
    jQuery.extend( {
        
        // once: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred)
        // memory: 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred)
        // unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调)
        // stopOnFalse: 当一个回调返回false 时中断调用
        Callbacks: function( options ) {
            options = typeof options === 'string' ?
                createOptions( options ) :
                jQuery.extend( {}, options )
                
            var
                firing, // list是否使用中
                memory, // { Callbacks对象, fire()的参数 }
                fired, // list是否使用过
                locked,
                list = [], // 观察者集合
                queue = [], // 保存着上一次调用fire()时传入的参数
                firingIndex = -1, // 当传入参数有memory时要用到这个参数定位
                
                fire = function() {
                    locked = locked || options.once;
                    
                    // list使用中
                    fired = firing = true;
                    
                    // 这个外层循环是个摆设,只循环1次
                    // 最重要的是执行完循环后firingIndex = -1
                    for ( ; queue.length; firingIndex = -1 ) {
                        memory = queue.shift();
                        
                        // 循环执行list中的函数
                        // 这代码的可读性真让人头皮发麻
                        // 有时候为了追求极简写法会让人摸不着头脑
                        while ( ++firingIndex < list.length ) {
    
                            // 执行函数并且检查是否需要提前终止
                            if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
                                options.stopOnFalse ) {
                                
                                firingIndex = list.length;
                                memory = false;
                            }
                        }
                    }
                    
                    // 如果options中没有memory,清空memory
                    // memory要用于add方法的判断,如果里面有值就会触发add中fire方法的调用
                    if ( !options.memory ) {
                        memory = false;
                    }
                    
                    // list使用结束
                    firing = false;
                    
                    // Clean up if we're done firing for good
                    if ( locked ) {
    
                        // Keep an empty list if we have data for future add calls
                        if ( memory ) {
                            list = [];
    
                        // Otherwise, this object is spent
                        } else {
                            list = "";
                        }
                    }
                },
                
                self = {
                    
                    // Add a callback or a collection of callbacks to the list
                    add: function() {
                        if ( list )  {
                            if ( memory && !firing ) {
                                firingIndex = list.length - 1;
                                queue.push( memory );
                            }
                            
                            // 这是一个立即执行的函数,而且声明的add方法也和自身没有任何关系,就是名字相同而已,不存在函数重写
                            ( function add( args ) {
                                // 传入的可能不止一个函数,多个函数就需要each一下
                                jQuery.each( args, function( _, arg ) {
                                    if ( isFunction( arg ) ) {
                                        // 两种情况
                                        // 1.如果options中没有unique
                                        // 2.如果options中有unique,但list中没有该函数
                                        if ( !options.unique || !self.has( arg ) ) {
                                            list.push( arg );
                                        }
                                    } else if ( arg && arg.length && toType( arg ) !== "string" ) { // arg有值 && arg长度不为0 && arg不为字符串(arg可能是个函数数组)
    
                                        // 递归下去
                                        add( arg );
                                    }
                                } );
                            } )( arguments );
                            
                            // 如果options中有memory,add过程中会立即执行当前函数,并且传入当前函数的参数是上一轮调用fire留下来的参数
                            if ( memory && !firing ) {
                                fire();
                            }
                        }
                        return this;
                    },
                    
                    // Remove a callback from the list
                    remove: function() {
                        // 老规矩,要移除的方法不止一个
                        jQuery.each( arguments, function( _, arg ) {
                            var index;
                            while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                // splice()方法从数组中删除项目
                                list.splice( index, 1 );
    
                                // Handle firing indexes
                                // firingIndex的值基本上为-1
                                // 如果出现index <= firingIndex,说明此时fire()方法正在运行
                                if ( index <= firingIndex ) {
                                    firingIndex--;
                                }
                            }
                        } );
                        return this;
                    },
                    
                    // 查找list中是否有传入的函数
                    has: function( fn ) {
                        return fn ?
                            jQuery.inArray( fn, list ) > -1 :
                            list.length > 0;
                    },
                    
                    // Remove all callbacks from the list
                    empty: function() {
                        if ( list ) {
                            list = [];
                        }
                        return this;
                    },
                    
                    // Disable .fire and .add
                    // Abort any current/pending executions
                    // Clear all callbacks and values
                    disable: function() {
                        locked = queue = [];
                        list = memory = "";
                        return this;
                    },
                    disabled: function() {
                        return !list;
                    },
    
                    // Disable .fire
                    // Also disable .add unless we have memory (since it would have no effect)
                    // Abort any pending executions
                    // 上锁:其表现结果就是不能调用fire()方法,但是能调用add
                    // 如果option是memory依然会在add()之后调用fire()方法
                    lock: function() {
                        // 设置locked和queue指向同一个地址
                        locked = queue = [];
                        if ( !memory && !firing ) {
                            list = memory = "";
                        }
                        return this;
                    },
                    locked: function() {
                        return !!locked;
                    },
                    
                    // Call all callbacks with the given context and arguments
                    fireWith: function( context, args ) {
                        if ( !locked ) {
                            args = args || [];
                            args = [ context, args.slice ? args.slice() : args ];
                            // 把[ context, 参数 ]放进queue里面
                            queue.push( args );
                            if ( !firing ) {
                                fire();
                            }
                        }
                        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;
        }
    } )
    

    相关文章

      网友评论

        本文标题:jQuery源码 Callbacks

        本文链接:https://www.haomeiwen.com/subject/pvhwdhtx.html