jQuery1.11源码分析,10-----Callbacks模块

Callbacks模块实质上就是一个回调函数队列(当然吹得很牛逼。。比如“提供了一种强大的方法来管理回调函数队列”),之所以介绍它是因为后面的Derferred模块基于它。

Callbacks生成时接收四个设置:once(只触发一遍),memory(记录前一次的触发传入参数,disable时是否清空队列),unique(确保队列中同样的函数只有一个),stopOnFalse(当调用某一个回调函数返回false时则停止触发)

例:jQuery.Callbacks('once memory')

Callbacks模块还有几个API,add,remove,has,empty,disable,lock,fire,fireWith(根据指定的上下文触发),以及两个状态判断函数,locked和disabled。

这几个API的用法就像名字所述的那样,增加啊,移除啊之类的。。更多信息可以看文档

这个模块没什么难度,在编写时主要有两个点需要考虑:1,当我添加或者删除某个函数时,该队列正在触发怎么办?2,当我要触发某一个队列时,该队列正在触发怎么办?

答:记录状态,做处理即可

//因为队列可能在调用时被改变,所以需要考虑两种状态,没调用和调用时。

jQuery.Callbacks = function( options ) {

        // Convert options from String-formatted to Object-formatted if needed
        // (we check in cache first)
    //这样处理一下以后调用就可以保证options存在,不会像我option&&option.xxx
        options = typeof options === "string" ?
                ( optionsCache[ options ] || createOptions( options ) ) :
                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存的不是fn,而是传给fn调用的arguments
        //这里名称上是stack,实际上是queue
                stack = !options.once && [],
                // Fire callbacks
        //data[0]是上下文
        //data[1]是传给各个回调函数的参数
                fire = function( data ) {
                        memory = options.memory && data;
                        fired = true;
                        firingIndex = firingStart || 0;
                        firingStart = 0;
                        firingLength = list.length;
                        firing = true;
                        for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                //当传入的data[0]为数组时,函数调用时的this依然是这个数组,而非数组中的item
                                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                                        memory = false; // To prevent further calls using add
                                        break;
                                }
                        }
                        firing = false;
            //这里主要考虑在执行的过程中会再次执行,所以用stack来保存传入的参数
                        if ( list ) {
                                if ( stack ) {
                                        if ( stack.length ) {
                                                fire( stack.shift() );
                                        }
                                } 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 ) {
                            console.log('list');
                            console.log(list);
                                                        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
                                console.log('递归');
                                                                add( arg );
                                                        }
                                                });
                                        })( arguments );
                                        // Do we need to add the callbacks to the
                                        // current firing batch?
                                        if ( firing ) {
                                                firingLength = list.length;
                                        // With memory, if we're not firing then
                                        // we should call right away
                    //如果有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;
                                                while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                                        //删除很简单就删除了
                            list.splice( index, 1 );
                                                        // Handle firing indexes
                            //需要考虑正在触发的情况
                                                        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;
};