• 读jQuery源码有感2


    那么就来读读jQuery源码的Callbacks部分。

    一上来看原版源码

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

    如你所见,一开始声明了好几个变量,英文注释也说明变量干什么用,但事实上,你还是不知道这些个变量具体作用在哪。

    那好,进入http://api.jquery.com/category/callbacks-object/

    这里每个api说明,都有个demo,每个demo就相当于一个测试用例。

    那么,依据测试用例,给源码设断点,一步步调试,看数据走向。但是一环嵌一环,绕来绕去,都记不住这些个变量变化的意义。

    反正,一,句,话。我不知道作者为什么这么写,这个模块的设计思路是怎样。

    怎么办,凉拌。

    既然有了测试用例,那我就根据它来自己实现。如果实现的过程中,遇到困难,再看看源码,这样就更接近作者的思维,从而知道那些变量,判断的意义所在。

    1.首先要实现的就是add和fire功能。

    其思路是,引入一个函数管理数组变量,add就是把函数加进数组里,fire就是遍历数组,一个个调用函数。

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>add和fire的功能</title>
        <script type="text/javascript">
        var callbacks = {
            callbacks_list: [],
            add: function(fn) {
                this.callbacks_list.push(fn);
            },
            fire: function() {
                var arg = arguments;
                this.callbacks_list.forEach(function(fn) {
                    fn.apply(this, arg);
                })
            }
        }
    
    
        var foo = function(value) {
            console.log("foo: " + value);
        };
    
    
        var bar = function(value) {
            console.log("bar: " + value);
        };
    
    
        callbacks.add(foo);
    
    
        callbacks.fire("hello");
    
    
    
        callbacks.add(bar);
    
    
        callbacks.fire("world");
        </script>
    </head>
    <body>
    现在的callback有add,fire功能
    </body>
    </html>

    2.加入remove功能。

    思路是,遍历函数管理数组,判断其成员和传进来的参数是否一致,一致则移除。

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>加入remove功能</title>
        <script type="text/javascript">
        var callbacks = {
            callbacks_list: [],
            add: function(fn) {
                this.callbacks_list.push(fn);
            },
            fire: function() {
                var arg = arguments;
                this.callbacks_list.forEach(function(fn) {
                    fn.apply(this, arg);
                })
            },
            remove: function(fn){
                for(var i=0, l = this.callbacks_list.length; i < l; i++){
                    if(this.callbacks_list[i] == fn){
                        this.callbacks_list.splice(i,1);
                    }
                }
            }
        }
    
        var foo = function(value) {
            console.log("foo: " + value);
        };
    
        var bar = function(value) {
            console.log("bar: " + value);
        };
    
        callbacks.add( foo );
         
        callbacks.fire( "hello" );
        callbacks.remove( foo );
         
        callbacks.fire( "world" );
        </script>
    </head>
    <body>
    现在的callback有add,fire,remove功能
    </body>
    </html>

    3.加入disable功能。

    disable在测试用例中所展示的意思就是,一旦disable了,再怎么fire,也不执行。

    内部实现就是,把函数管理数组设置为undefined,然后在fire那边判断undefined,则跳出函数。

    我这里忘了在add函数里,判断函数管理数组callbacks_list的undefined。

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>3.加入disabled功能</title>
        <script type="text/javascript">
        var callbacks = {
            callbacks_list: [],
            add: function(fn) {
                this.callbacks_list.push(fn);
            },
            fire: function() {
                if(this.callbacks_list == undefined || this.callbacks_list == null){
                    return;
                }
                var arg = arguments;
                this.callbacks_list.forEach(function(fn) {
                    fn.apply(this, arg);
                })
            },
            remove: function(fn) {
                for (var i = 0, l = this.callbacks_list.length; i < l; i++) {
                    if (this.callbacks_list[i] == fn) {
                        this.callbacks_list.splice(i, 1);
                    }
                }
            },
            disable: function() {
                this.callbacks_list = undefined;
                return this;
            },
            disabled: function() {
                return !callbacks_list;
            }
        }
        var foo = function(value) {
            console.log(value);
        };
        callbacks.add(foo);
    
    
        callbacks.fire("foo");
    
        callbacks.disable();
    
        callbacks.fire("foobar");
        // foobar isn't output
        </script>
    </head>
    <body>
    现在的callback有add,fire,remove,disable功能
    </body>
    </html>

    4.加入has功能

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>4.加入has功能</title>
        <script type="text/javascript">
        var callbacks = {
            callbacks_list: [],
            add: function(fn) {
                this.callbacks_list.push(fn);
            },
            fire: function() {
                if (this.callbacks_list == undefined || this.callbacks_list == null) {
                    return;
                }
                var arg = arguments;
                this.callbacks_list.forEach(function(fn) {
                    fn.apply(this, arg);
                })
            },
            remove: function(fn) {
                for (var i = 0, l = this.callbacks_list.length; i < l; i++) {
                    if (this.callbacks_list[i] == fn) {
                        this.callbacks_list.splice(i, 1);
                    }
                }
            },
            disable: function() {
                this.callbacks_list = undefined;
                return this;
            },
            disabled: function() {
                return !callbacks_list;
            },
            has: function(fn) {
                for (var i = 0, l = this.callbacks_list.length; i < l; i++) {
                    if (this.callbacks_list[i] == fn) {
                        return true;
                    }
                }
                return false;
            }
        }
        var foo = function(value1, value2) {
            console.log("Received: " + value1 + "," + value2);
        };
    
        // A second function which will not be added to the list
        var bar = function(value1, value2) {
            console.log("foobar");
        };
    
        // Add the log method to the callbacks list
        callbacks.add(foo);
    
        // Determine which callbacks are in the list
        console.log(callbacks.has(foo));
        // true
        console.log(callbacks.has(bar));
        // false
    
        </script>
    </head>
    <body>
    现在的callback有add,fire,remove,disable,has功能
    </body>
    </html>

    5.加入empty功能

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>5.加入empty功能</title>
        <script type="text/javascript">
        var callbacks = {
            callbacks_list: [],
            add: function(fn) {
                this.callbacks_list.push(fn);
            },
            fire: function() {
                if (this.callbacks_list == undefined || this.callbacks_list == null) {
                    return;
                }
                var arg = arguments;
                this.callbacks_list.forEach(function(fn) {
                    fn.apply(this, arg);
                })
            },
            remove: function(fn) {
                for (var i = 0, l = this.callbacks_list.length; i < l; i++) {
                    if (this.callbacks_list[i] == fn) {
                        this.callbacks_list.splice(i, 1);
                    }
                }
            },
            disable: function() {
                this.callbacks_list = undefined;
                return this;
            },
            disabled: function() {
                return !callbacks_list;
            },
            has: function(fn) {
                for (var i = 0, l = this.callbacks_list.length; i < l; i++) {
                    if (this.callbacks_list[i] == fn) {
                        return true;
                    }
                }
                return false;
            },
            empty:function(){
                this.callbacks_list = [];
                return this;
            }
        }
        // A sample logging function to be added to a callbacks list
        var foo = function(value1, value2) {
            console.log("foo: " + value1 + "," + value2);
        };
    
        // Another function to also be added to the list
        var bar = function(value1, value2) {
            console.log("bar: " + value1 + "," + value2);
        };
    
    
        // Add the two functions
        callbacks.add(foo);
        callbacks.add(bar);
    
        // Empty the callbacks list
        callbacks.empty();
    
        // Check to ensure all callbacks have been removed
        console.log(callbacks.has(foo));
        // false
        console.log(callbacks.has(bar));
        // false
    
        </script>
    </head>
    <body>
    现在的callback有add,fire,remove,disable,has,empty功能
    </body>
    </html>

    6.加入lock功能

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>6.加入lock功能</title>
        <script type="text/javascript">
        var callbacks = {
            callbacks_list: [],
            memory: "",
            has_locked : false,
            add: function(fn) {
                this.callbacks_list.push(fn);
            },
            fire: function() {
                if (this.callbacks_list == undefined || this.callbacks_list == null) {
                    return;
                } else {
                    if(this.has_locked == true){
                        var callback_this = this;
                        var arg = callback_this.memory
                        this.callbacks_list.forEach(function(fn) {
                            fn.apply(callback_this, arg);
                        })
                    }
                    else{
                        var arg = arguments;
                        var callback_this = this;
                        this.memory = arg;
                        this.callbacks_list.forEach(function(fn) {
                            fn.apply(callback_this, arg);
                        })
                    }
                        
                }
    
            },
            remove: function(fn) {
                for (var i = 0, l = this.callbacks_list.length; i < l; i++) {
                    if (this.callbacks_list[i] == fn) {
                        this.callbacks_list.splice(i, 1);
                    }
                }
            },
            disable: function() {
                this.callbacks_list = undefined;
                return this;
            },
            disabled: function() {
                return !callbacks_list;
            },
            has: function(fn) {
                for (var i = 0, l = this.callbacks_list.length; i < l; i++) {
                    if (this.callbacks_list[i] == fn) {
                        return true;
                    }
                }
                return false;
            },
            empty: function() {
                this.callbacks_list = [];
                return this;
            },
            lock: function() {
                this.callbacks_list = [];
                this.has_locked = true;
            }
        }
        /*demo1*/
        var foo = function(value) {
            console.log("foo:" + value);
        };
        var bar = function(value) {
            console.log("bar:" + value);
        };
    
    
        callbacks.add(foo);
    
        callbacks.fire("hello");
    
        callbacks.lock();
    
        callbacks.fire("world");
    
        /*demo2*/
        // Add the foo function to the callback list again
        callbacks.add(foo);
    
        // Try firing the items again
        callbacks.fire("silentArgument");
        // Outputs "foo: hello" because the argument value was stored in memory
    
        // Add the bar function to the callback list
        callbacks.add(bar);
    
        callbacks.fire("youHadMeAtHello");
    
        </script>
    </head>
    <body>
    现在的callback有add,fire,remove,disable,has,empty,lock功能
    </body>
    </html>

    还有未完成的就是,$.Callbacks()的参数once,memory,unique,stopOnFalse。

    我一直思考的是,代码构建过程和纯逻辑思路,代码阅读。

    一般来说,一个库的形成,是因为作者编写的过程中,发现一个个需求,或者一个个bug,然后需求和bug形成测试用例,根据测试用例,构造库。

    它的库代码最后为什么会这个样子,一次次引入新变量,一个个判断来嵌套,还有为了库的使用便利,对函数参数做了各种判断处理,导致直接阅读有难度,不能通俗易懂。

    那我们能否根据测试用例提取出这个模块的纯逻辑思路。至于逻辑思路的实现,交给开发自己代码能力实现。

  • 相关阅读:
    转:MVC分页
    转:更新Android SDK之后Eclipse提示ADT版本过低的一个简易解决办法
    DataGridView 添加鼠标右键选择行
    WinForm 中使用ScintillaNet
    C#获取当前程序运行路径的方法集合
    Winform DataGridView鼠标右键选择列
    EF 数据查询(更改默认排序)
    使用lambda表达式进行对象结合的筛选操作
    使用SSIS生成数据导出为Excel文件的模板
    Sql server 数据库中计算每天的开始结束时间
  • 原文地址:https://www.cnblogs.com/samwu/p/3473216.html
Copyright © 2020-2023  润新知