读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形成测试用例,根据测试用例,构造库。

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

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

原文地址:https://www.cnblogs.com/samwu/p/3473216.html