• jquery的2.0.3版本源码系列(6):2880-3042行,回调对象,对函数的统一管理


    目录

    1 . 回调对象callbacks的演示

    回调的使用有一点像事件绑定,先绑定好,等到有点击事件或者其他时就触发。

     <script src="js/jquery-2.0.3.js"></script>
        <script>
            function aaa(){
                alert(1);
            }
            function bbb(){
                alert(2);
            }
            function ccc(){
                alert(3);
            }
            var cb=$.Callbacks();
            cb.add(aaa);
            cb.add(bbb);
            cb.add(ccc);
            cb.fire();
            //看起来有点像事件绑定,add负责添加,fire负责触发
        </script>

    工作原理相当于add方法负责收集事件list,fire负责统一触发,触发时以for循环来做到。

    回调对象的好处是统一管理。

    看一个未使用回调对象的例子:

    <script src="js/jquery-2.0.3.js"></script>
        <script>
           function aaa(){
               alert(1);
           }
           (function(){
               function bbb(){
                   alert(2);
               }
           })();
           aaa();//可调用
            bbb();//报错,说not defined
        </script>

    那么为了能够调用匿名函数里的bbb函数,可以绑定到全局的回调对象上。

    <script src="js/jquery-2.0.3.js"></script>
    <script>
            var  cb=$.Callbacks();
            function aaa(){
                alert(1);
            }
            cb.add(aaa);
            (function(){
                function bbb(){
                    alert(2);
                };
                cb.add(bbb);
            })();
           cb.fire();//依次弹出1和2
    </script>

    2.callbacks的参数说明                                                                  

    1.4个选项

    回调函数有once、memory、unique、stopOnFalse个选项。

    once的作用是只能触发一次。

    <script src="js/jquery-2.0.3.js"></script>
        <script>
            function aaa(){
                alert(1);
            }
            function bbb(){
                alert(2);
            }
            var cb=$.Callbacks('once');
            cb.add(aaa);
            cb.add(bbb);
            cb.fire();//生效
            cb.fire();//并不生效
        </script>

    memory的作用是记忆功能。

    <script src="js/jquery-2.0.3.js"></script>
        <script>
            function aaa(){
                alert(1);
            }
            function bbb(){
                alert(2);
            }
            var cb=$.Callbacks('memory');
            cb.add(aaa);
            cb.fire();//两个函数都生效
            cb.add(bbb);
        </script>

    unique的作用是对相同的函数去重。

    <script src="js/jquery-2.0.3.js"></script>
        <script>
            function aaa(){
                alert(1);
            }
            function bbb(){
                alert(2);
            }
            var cb=$.Callbacks('unique');
            cb.add(aaa);
            cb.add(aaa);
            cb.fire()//这样就只触发一次aaa
        </script>

    stopOnFalse的作用是当函数返回值为false时就不再执行后续函数。

    <script src="js/jquery-2.0.3.js"></script>
        <script>
            function aaa(){
                alert(1);
                return false;
            }
            function  bbb(){
                alert(2);
            }
            var cb=$.Callbacks('stopOnFalse');
            cb.add(aaa);
            cb.add(bbb);
            cb.fire()//遇到返回False,那么后续就不再执行
        </script>

    另:callbacks接收多个选项的组合,比如 var cb=$.Callbacks('once unique'); 。

    2.options源码

    有3个部分

    var optionsCache = {};

    这里定义了选项缓存的空对象。

    function createOptions( options ) {
        var object = optionsCache[ options ] = {};
            //定义了一个options作为属性的对象
        jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
            object[ flag ] = true;
        });
          //options.match( core_rnotwhite )根据空格分割字符串,并返回结果数组
        return object;
    }

    那么传进来的选项options进行字符串分割, var cb=$.Callbacks('once unique');  的option匹配的结果是 ['one','unique'] 。接下来在each循环里第一个选项时索引,第二个选项分别是one和unique。

    所以把选项存进optionsCache并返回object。

    <script>
            var optionsCache={};
            function  cache (options){
                var object = optionsCache[ options ] = {};
                object[options]=true;
                console.log(object);//{once:true}
            }
            cache('once');
        </script>
    创建选项object
    options = typeof options === "string" ?
            ( optionsCache[ options ] || createOptions( options ) ) :
            jQuery.extend( {}, options );

    这段代码判断了options是不是字符串,如果不是,比如 $.Callbacks() 那么返回的就是空对象。如果是字符串,创建固定格式的选项,能从缓存里面取就直接取,不能就构造出来。类似如下:

    optionsCache:{
                'once memory':{once:true,memory:true}
            }
    options:{once:true,memory:true}

    3.1  定义了一些变量                                     

        1.memory

        2.fired

        3.firing

        4.firingStart

        5.firingLength

        6.firingIndex

        7.list是实际的回调列表

        这是回调对象的最重要的一个数组了,通过把回调函数push到list中,以此来决定触发的列表。

       8.stack

         stack = !options.once && [] 通过代码可以看到如果选项的once为true,那么结果为false,过选项的once为false也就是不要设置,那么stack就为true了。

       9.fire方法

        它是一个辅助方法,供self对象的fire、fireWith等调用。

       10.self对象

       定义了对外的方法。一旦使用jQuery.callbacks就会返回self对象,那么其间定义的方法就可以被调用了。

    3.2  add方法                                             

        add方法放在self对象里。

    1.分析无参的情况。为了测试这个方法做了什么。这里介绍一个执行源代码的小技巧。先把代码设置为

    <script src="js/jquery-2.0.3.js"></script>
        <script>
            function aaa() {
                alert(1);
            }
            function bbb(){
                alert(2);
            }
            var cb=$.Callbacks();
        </script>
    还未使用add方法
    var start = list.length;
    console.log(arguments);
    源代码添加控制台log方法

    这里的打印是代码初始化的过程。

    准备工作好了。

    脚本代码如下:

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

    这个时候的console.log打印,那么内置arguments对象就分别是aaa和bbb了。

    为什么会each遍历呢,针对的是 cb.add(aaa,bbb); ,那么argument对象就有了2个元素了。

    self = {
                // 添加一个回调或者一个回调列表
                add: function() {
                    if ( list ) {
                        //第一次进入的时候当然list空数组,start等于0,第二次等于1.
                        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" ) {
                                    // 这里处理的是add([aaa,bbb])数组类型的参数
                                    add( arg );
                                }
                            });
                        })( arguments );
                      
                       .........
                    }
                    return this;
                },

    通过一个自执行add匿名函数,对arguments进行遍历。

    如果为function进入if分支。这个时候有一个unique判断。当然无参的时候unique是undefined咯,所以会进入list.push方法。那么也就是说所有添加的函数都会被push到list中。

    如果出现数组参数,比如 cb.add([aaa,bbb]); ,那么就把这个数组再递归调用一遍,当然是遍历数组咯。

     2.处理unique参数

    if ( !options.unique || !self.has( arg ) ) {
                                        list.push( arg );
                                    }

    我们看到unique一开始为true,那么 !options.unique 第一次进入时为false,就要看self是否有arg了。第一次添加某个函数,false取反就为true。第二次添加相同的函数,true取反就为false了。所以第二次添加相同的函数时,是不可能push到list中的。那么也就实现了unique的唯一添加目标了。

    3.处理memory参数

    我们知道memory参数的作用是如果回调已经被触发了,那么再次添加add方法,会自动触发。

     <script src="js/jquery-2.0.3.js"></script>
        <script>
            function aaa() {
                alert(1);
            }
            function bbb(){
                alert(2);
            }
            var cb=$.Callbacks("memory");
            cb.add(aaa);
            cb.fire();
            cb.add(bbb);//弹出2
    
        </script>

    那么在add方法里,memory是什么呢,搜索一下,2909行memory等于之前触发的data memory = options.memory && data; 。

    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 );
                        }

    比如,

    <script src="js/jquery-2.0.3.js"></script>
        <script>
            function aaa(n) {
                alert(n);
            }
            function bbb(){
                alert(2);
            }
            var cb=$.Callbacks("memory");
            cb.add(aaa);
            cb.fire(999);//fire函数传参
            cb.add(bbb);//弹出2
    
        </script>

    这个时候memory就被赋值为如下打印内容,自然是包含传进去的参数999的。同时因为add方法里的start赋值,现在已经变为了1,通过firingStart的矫正,那么就只触发list最后一个函数了。

     

    3.3   remove方法

    // 从触发list里移除某个回调
                remove: function() {
                    if ( list ) {
                        jQuery.each( arguments, function( _, arg ) {
                            var index;
    //  先遍历触发列表,然后通过splice方法裁减掉
                            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;
                },

    首先上脚本代码。一旦函数aaa被remove掉,就不会被触发。

     <script src="js/jquery-2.0.3.js"></script>
        <script>
            function aaa() {
                alert(1);
            }
            function bbb(){
                alert(2);
            }
            var cb=$.Callbacks();
            cb.add(aaa,bbb);
            cb.remove(bbb);
            cb.fire();//bbb并不会被触发
        </script>

    其原理是从list中删除掉。

    根据源码,先找出arguments,其打印结果为

    那么接下来就看源码执行了。

    通过each方法可以把要remove掉的函数取出来。 ( index = jQuery.inArray( arg, list, index ) ) > -1 是把remove掉的函数在list中取到索引,然后通过slice删除对应的元素。就做到了从list中删除某个函数的触发了。

    最后看firing跟onStopFalse参数有关。参看fire方法。

    3.4  has方法

     <script>
            function aaa() {
                alert(1);
            }
            function bbb(){
                alert(2);
            }
            var cb=$.Callbacks();
            cb.add(aaa);
            cb.has(aaa);//true
        </script>
    has: function( fn ) {
                    return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
                },
    //inArray方法传两个参数,作用是fn在数组list里的索引。大于-1当然是存在咯。
    //fn不存在比如cb.has(),就返回list和list的长度的逻辑值,自然是true。
    //list本来就不存在,那么是false

    如果传进去一个函数,那么进行条件判断,返回true或者FALSE。

    3.5  empty方法

    //从list中删除所有的回调
                empty: function() {
                    list = [];
                    firingLength = 0;
                    return this;
                },

    要清空所有的回调要做两件事情,首先list变为空数组,然后firingLength置为0。

    3.6  disable、diabled、lock方法

      // 禁用
    disable: function() {
         list = stack = memory = undefined;
         return this;
    },
    // 是否被禁用了
    disabled: function() {
         return !list;
    },
    // 在当前的状态锁住list
        lock: function() {
        stack = undefined;
          if ( !memory ) {
        self.disable();
          }
         return this;
    },
    // 它是否locked
        locked: function() {
        return !stack;
    },

    3.7  fire一系列的方法

     首先看fire方法。

    第一,我们需要了解的是传进去的data是什么。在回调对象中,供3个地方调用, fire( memory ); 、 fire( args ); 和 fire( stack.shift() ); 。为了简便,通过第三处代码的分析。

    第二,stack到底从2903行的 stack = !options.once && [], 发生了什么。这里的意思是如果once是false的话,就会把空数组放到stack中。搜索stack可以看到只是在3023行有一个push动作。

    fire = function( data ) {
                memory = options.memory && data;
    //通过选项的memory返回true或者false
                fired = true;
    //把fired置为true
                firingIndex = firingStart || 0;
    //触发的起始索引,要么是firingStart,要么是默认0
                firingStart = 0;
                firingLength = list.length;
    //触发的长度为list的长度
                firing = true;
    //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();
                    }
                }
            },

    接下来看实例方法。

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

    最后的fired的意思是是否被触发过了。

    // To know if the callbacks have already been called at least once
                fired: function() {
                    return !!fired;
                }
  • 相关阅读:
    重磅!阿里发布《Java开发手册(泰山版)》
    IntelliJ IDEA 快捷键终极大全,速度收藏!
    Java多态的实现机制是什么,写得非常好!
    Intellij IDEA 非常 6 的 10 个姿势!
    深入浅出Java中的clone克隆方法,写得太棒了!
    Java集合类,一张图说清楚!
    在 IntelliJ IDEA 中使用 Git,太方便了!.md
    Http(get,post)及HttpClient(get,post)的简单使用
    java servlet+jquery+json学习小例子
    JAVA中使用JSON进行数据传递
  • 原文地址:https://www.cnblogs.com/chenmeng2062/p/7459505.html
Copyright © 2020-2023  润新知