zoukankan      html  css  js  c++  java
  • jquery源码 Callback

    工具方法。对函数的统一管理。

    jquery2.0.3版本$.Callback()部分的源码如下:

    // String to Object options format cache
    var optionsCache = {};
    
    // 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;
        });
        return object;
    }
    
    /*
     * 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:            will ensure the callback list can only be fired once (like a Deferred)
     *
     *    memory:            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:            will ensure a callback can only be added once (no duplicate in the list)
     *
     *    stopOnFalse:    interrupt callings when a callback returns false
     *
     */
    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 ] || 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 = 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 ) {
                        memory = false; // To prevent further calls using add
                        break;
                    }
                }
                firing = false;
                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 ) {
                                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?
                        if ( firing ) {
                            firingLength = list.length;
                        // With memory, if we're not firing then
                        // we should call right away
                        } 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;
    };
    View Code

    一、$.Callback()的简单使用及应用场景

    1、$.Callback()的用法。

    观察者模式,添加完后统一触发。

           function aaa(){
                alert(1);
            }
            function bbb(){
                alert(2);
            }
            var cb= $.Callbacks();
            cb.add(aaa);
            cb.add(bbb);
            cb.fire();    

    2、好处,应用场景。

    要统一的管理aaa和bbb。有时候如下,很难对不同作用域下的函数进行统一管理。

      function aaa(){
                alert(1);
            }
            (function(){
                function bbb(){
                    alert(2);
                }
            })();
            aaa();
            bbb();

    只能弹出1,因为bbb是局部作用域中的。

    $callback可以做到。如下,只要cb是全局的。

     var cb= $.Callbacks();
            function aaa(){
                alert(1);
            }
            cb.add(aaa);
            (function(){
                function bbb(){
                    alert(2);
                }
                cb.add(bbb);
            })();
            cb.fire();

    对应复杂情况很有用。统一管理,通过fire统一触发。

    二、原理图

    Callback接收一个参数,可以有4个选项,once,memory,unique,stopOnFalse。

    self单体有这些方法:add,remove,has,empty,disable,disabled,lock,locked, fireWith,fire,fired。

    list=[]数组变量,用来收集回调函数。fire的时候对其循环调用。

    add:push数组

    fire:调用fireWith,fireWith允许传参,fire可传可不传。

    fireWith:调用私有函数fire,在私有函数fire中for循环list。

    remove:splice数组。

    4个参数:

    • once针对fire()只循环一次
    • memory 针对add,作用到add上,add时判断有memory就去执行fire。
    • unique 针对add,添加的时候就可以去重
    • stopOnFalse 针对fire,在for循环时遇到false,立即跳出循环

    三、更多用法

    1、callback4个参数的作用

    • once: 只能够触发一次。
    • memory: 当队列已经触发之后,再添加进来的函数就会直接被调用,不需要再触发一次。
    • unique: 保证函数的唯一
    • stopOnFalse: 只要有一个回调返回 false,就中断后续的调用。

    举例:

    不传参数,fire几次就触发几次。

    function aaa() {
                alert(1);
            }
    
            function bbb() {
                alert(2);
            }
            var cb = $.Callbacks();
            cb.add(aaa);
            cb.add(bbb);
            cb.fire(); //1 2
            cb.fire();//1 2
    View Code
    • once:fire只能触发一次,源码中fire后如果有once就把list干掉了,list=undefined了。
            function aaa() {
                alert(1);
            }
    
            function bbb() {
                alert(2);
            }
            var cb = $.Callbacks('once');
            cb.add(aaa);
            cb.add(bbb);
            cb.fire(); //1 2
            cb.fire();

    不传参数,在fire之后add的回调不能被fire。

    //不写参数,只弹出1,2不会弹出
     function aaa() {
                alert(1);
            }
    
            function bbb() {
                alert(2);
            }
            var cb = $.Callbacks();
            cb.add(aaa);
            cb.fire(); //1
            cb.add(bbb);
    View Code
    • memory记忆,在fire前面后面add的方法都能得到执行。
    function aaa() {
                alert(1);
            }
    
            function bbb() {
                alert(2);
            }
            var cb = $.Callbacks('memory');
            cb.add(aaa);
            cb.fire(); //1 2
            cb.add(bbb);
    • unique:去重
    //不加参数,add2次aaa,就会触发2次aaa
    function aaa() {
                alert(1);
            }
    
    
            var cb = $.Callbacks();
            cb.add(aaa);
            cb.add(aaa);
            cb.fire(); //1 1
    View Code
    function aaa() {
                alert(1);
            }
    
    
            var cb = $.Callbacks('unique');
            cb.add(aaa);
            cb.add(aaa);
            cb.fire(); //1 加了unique参数,同样的函数不能多次add
    • stopOnFalse:函数返回false跳出循环
    function aaa() {
                alert(1);
                return false;
            }
            function bbb() {
                alert(2);
            }
    
            var cb = $.Callbacks();
            cb.add(aaa);
            cb.add(bbb);
            cb.fire(); //1 2 不传参,第一个函数返回false时后面的函数也能正常执行
    function aaa() {
                alert(1);
                return false;
            }
            function bbb() {
                alert(2);
            }
    
            var cb = $.Callbacks('stopOnFalse');
            cb.add(aaa);
            cb.add(bbb);
            cb.fire(); //1
            //传参stopOnFalse,第一个函数返回false时后面的函数不再执行

    2、callback也可以接收组合的形式

     function aaa() {
                alert(1);
            }
            function bbb() {
                alert(2);
            }
            //组合使用,只执行一次,并且弹出1 2
            var cb = $.Callbacks('once memory');
            cb.add(aaa);
            cb.fire(); //1
            cb.add(bbb);
            cb.fire();

    源码中:
    传入了 once和memory后,

    options={once:true,memory:true}
    optionCache={
    
    "once memory":{once:true,memory:true}
    }

    6、fire()可以传参

    参数作为每个回调函数的实参

    function aaa(n) {
                alert("aaa "+n);
            }
            function bbb(n) {
                alert("bbb "+n);
            }
            var cb = $.Callbacks();
            cb.add(aaa);
            cb.add(bbb);
            //fire传参
            cb.fire("hello"); //弹出aaa hello 和bbb hello

    四、源码

    Callbacks就是一个工具函数,内部定义了一个self ,add和remove还有has等挂在self上。

    1、参数处理

    $.Callbacks有4个可选的参数,可以组合传入,用空格分隔。比如 $.Callbacks("once memory unique");

    这样传入的构造函数字符串实际上是一个字符串,源码中做了处理会把这个字符串转成对象。

    // String to Object options format cache
    var optionsCache = {};
    
    // 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;
        });
        return object;
    }

    在构造函数中传入一个options后,先进行如下处理调用。把一个字符串处理成一个对象。

    传入的options="once memory unique"处理后options={once:true,memory:true,unique:true}

        // Convert options from String-formatted to Object-formatted if needed
        // (we check in cache first)
        options = typeof options === "string" ?
            ( optionsCache[ options ] || createOptions( options ) ) :
            jQuery.extend( {}, options );

    过程如下:options="once memory unique"是string类型,所以先从optionsCache中获取,现在optionsCache为{}所以optionsCache[ options ]是undefined走后面的createOptions( options ) 。create操作中先新建一个以options为键的空对象,再循环给对象中填充。循环操作完

    optionCache为

    optionCache={ "once memory unique":{once:true,memory:true,unique:true} }

    options为

    options={once:true,memory:true,unique:true}

    2、add源码

     主要是把回调函数Push到数组list中。

           add: function() {
                if ( list ) { //list初始化为[],if判断会返回true
                    // First, we save the current length
                    var start = list.length;
                    (function add( args ) {
                        jQuery.each( args, function( _, arg ) { ////处理cb.add(aaa,bbb)这种调用
                            var type = jQuery.type( arg );//arg就是每一个函数
                            if ( type === "function" ) {//arg是函数就push到list中,此时有个判断有没有unique
                                if ( !options.unique || !self.has( arg ) ) {//有unique走后面,判断list中有没有这个函数,有就不添加了
                                    list.push( arg );
                                }
                            } else if ( arg && arg.length && type !== "string" ) { //处理cb.add([aaa,bbb])这种调用
                                // Inspect recursively
                                add( arg );//递归分解,最终还是push到list
                            }
                        });
                    })( 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
                    } else if ( memory ) {
                        firingStart = start;
                        fire( memory );
                    }
                }
                return this;
            },

    3、remove源码

    // 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 );//主要就是splice删除操作
                            // Handle firing indexes
                            if ( firing ) {
                                if ( index <= firingLength ) {
                                    firingLength--;
                                }
                                if ( index <= firingIndex ) {
                                    firingIndex--;
                                }
                            }
                        }
                    });
                }
                return this;
            },

    4、fire源码

    1、整体调用逻辑

    self的fire调用self的fireWith,fireWith把参数传递到fire()函数。

    // 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;
                },

    fire()时主要是for循环

     // Fire callbacks
            fire = function( data ) {
                memory = options.memory && data;
                fired = true;//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 ) {//每次函数调用同时处理stopOnFalse的情况
                        memory = false; // To prevent further calls using add //stopOnFalse后有memory也不好使了
                        break;
                    }
                }
                firing = false;//触发结束
                if ( list ) {
                    if ( stack ) {
                        if ( stack.length ) {
                            fire( stack.shift() );
                        }
                    } else if ( memory ) {
                        list = [];
                    } else {
                        self.disable();
                    }
                }
            },

    2、firing特殊情况

    比如在 fire 处理队列中,某个函数又在队列中添加了一个回调函数,或者,在队列中又删除了某个回调函数。 fire 处理过程中,某个函数又调用了 fire 来触发事件呢?

    先通过例子来看一下效果

            function aaa() {
                alert(1);
                cb.fire(); //在这里调用fire()会出现什么问题 死循环
            }
            function bbb() {
                alert(2);
            }
            var cb = $.Callbacks();
            cb.add(aaa);
            cb.add(bbb);
    
            cb.fire(); 

    在执行函数的过程中再次调用fire()的执行顺序是怎样的?

    var bBtn=true;//用bBtn避免死循环
            function aaa() {
                alert(1);
                if(bBtn){
                    cb.fire();//注意这里fire调用后执行顺序是1 2 1 2,而不是1 1 2 2
                    bBtn=false;
                }
    
            }
            function bbb() {
                alert(2);
            }
            var cb = $.Callbacks();
            cb.add(aaa);
            cb.add(bbb);
    
            cb.fire();

    结论:把函数运行过程中触发的fire()放到了运行过程的队列当中。

    fire 处理过程中,某个函数又调用了 fire 来触发事件时,jQuery的处理方式如下:

    将这个嵌套的事件先保存起来,等到当前的回调序列处理完成之后,再检查被保存的事件,继续完成处理。显然,使用队列是处理这种情况的理想数据结构,如果遇到这种状况,我们就将事件数据入队,待处理的时候,依次出队数据进行处理。什么时候需要这种处理呢?显然不是once的情况。在JavaScript中,堆队列也是通过数组来实现的,push用来将数据追加到数组的最后,而shift用来出队,从数据的最前面获取数据。

    不过,jQuery没有称之为队列,而是取名stack。

    // Stack of fire calls for repeatable lists
    stack = !options.once && [],

    入队

    源码中,在fireWith的时候判断for循环有没有执行完

     fireWith: function( context, args ) {
                ...if ( firing ) {//firing在for循环没有走完时一直是true
                        stack.push( args );//所以这句话意思就是函数执行时再去fire()调用就会push到stack数组中
                    } else {
                        fire( args );
                    }
                }
                return this;
            },

    出队

    再去调用fire()的时候

      // Fire callbacks
            fire = function( data ) {
                memory = options.memory && data;
                fired = true;//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 ) {//每次函数调用同时处理stopOnFalse的情况
                        memory = false; // To prevent further calls using add //stopOnFalse后有memory也不好使了
                        break;
                    }
                }
                firing = false;//触发结束
                if ( list ) {
                    if ( stack ) {        //这就是出现在函数执行过程中再次fire()的时候,等循环执行完,再去按顺序执行
                        if ( stack.length ) {
                            fire( stack.shift() );
                        }
                    } else if ( memory ) {//只执行一次的时候,有once,memory就清空list,此时fire()就相当于一个执行一个空数组
                        list = [];
                    } else {
                        self.disable();//disable阻止后续任何的fire()操作
                    }
                }
            },

    针对下面这段源码的一个例子:

    once和memory同时存在的时候,fire()无效因为list为[]了,但是add仍然有效。

    当有memory的时候,把之前添加的清空;允许添加并再次运行fire后清空;当不存在memory的时候既只有once配置,fire之后既不允许做任何操作了。

    else if ( memory ) {//只执行一次的时候,有once,memory就清空list,此时fire()就相当于一个执行一个空数组
                        list = [];
                    } else {
                        self.disable();//disable阻止后续任何的fire()操作
                    }

    disable阻止后续任何的fire()操作。

     function aaa() {
                alert(1);
            }
            function bbb() {
                alert(2);
            }
            //组合使用,只执行一次,并且弹出1 2 3
            var cb = $.Callbacks('once memory');
            cb.add(aaa);
            cb.fire(); //1
            cb.fire();//此时list为[]
            cb.add(bbb);
            cb.fire();
            function ccc(){
                alert(3);
            }
            cb.add(ccc);

    5、其他源码

    has(fn):判断list有没有fn

    empty: 清空数组list=[]

    disable:全部锁住,禁止了,如下

    // Have the list do nothing anymore
                disable: function() {
                    list = stack = memory = undefined;
                    return this;
                },

    disabled:判断是不是禁止了。return !list;

    lock:只是把stack锁住

    // Lock the list in its current state
                lock: function() {
                    stack = undefined;
                    if ( !memory ) {
                        self.disable();
                    }
                    return this;
                },

    locked:是否locked。 return !stack;

    6、 lock和disable的区别

    disable禁止所有操作

     function aaa() {
                alert(1);
            }
            function bbb() {
                alert(2);
            }
    
            var cb = $.Callbacks('memory');
            cb.add(aaa);
            cb.fire(); //1
            cb.disable();//disable()后只能弹出1 因为禁止所有操作了,虽然有Memory
            cb.add(bbb);//不起作用了,此时list变为undefined
            cb.fire();//不起作用了

    lock只是锁住数组

    function aaa() {
                alert(1);
            }
            function bbb() {
                alert(2);
            }
    
            var cb = $.Callbacks('memory');
            cb.add(aaa);
            cb.fire(); //1 2
            cb.lock();//lock()只是把后续的fire()锁住,其他操作是锁不住的
            cb.add(bbb);
            cb.fire();//不起作用了 此时list为[]
    
    

    参考:

    http://www.cnblogs.com/haogj/p/4473477.html

    本文作者starof,因知识本身在变化,作者也在不断学习成长,文章内容也不定时更新,为避免误导读者,方便追根溯源,请诸位转载注明出处:http://www.cnblogs.com/starof/p/6885500.html有问题欢迎与我讨论,共同进步。

  • 相关阅读:
    cmanformat
    mysql-sql语言参考
    jQuery 判断多个 input checkbox 中至少有一个勾选
    Java实现 蓝桥杯 算法提高 计算行列式
    Java实现 蓝桥杯 数独游戏
    Java实现 蓝桥杯 数独游戏
    Java实现 蓝桥杯 数独游戏
    Java实现 蓝桥杯 算法提高 成绩排序2
    Java实现 蓝桥杯 算法提高 成绩排序2
    Java实现 蓝桥杯 算法提高 成绩排序2
  • 原文地址:https://www.cnblogs.com/starof/p/6885500.html
Copyright © 2011-2022 走看看