zoukankan      html  css  js  c++  java
  • jquery的回调对象Callbacks详解

    Callbacks : 对函数的统一管理

    Callbacks的options参数接受4个属性,分别是
    once : 只执行一次
    momery : 记忆
    stopOnFalse : 强制退出循环
    unique : 唯一

    暂时先不管4个属性有什么意思,我们看代码开始部分对options做了处理,如果options是字符串则调用createOptions方法转成json
    比如:var cb = $.Callbacks('once momery');
    转换为:{once:true,momery:true}
    这里就不贴createOptions的源码了,大家可以自己去看一下。

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


    处理完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 ) {
    
                        ..................
                    },
                    self = {
                        add: function() {
    
                            ..............
                        },
                        remove: function() {
    
                            ................
    
                        },
                        has: function( fn ) {
    
                            ..................
                        },
                        empty: function() {
    
                            ..................
                        },
                        disable: function() {
    
                            ..................
                        },
                        disabled: function() {
        
                            ................
                        },
                        lock: function() {
    
                            ..............
                        },
                        locked: function() {
    
                            ..............
                        },
                        fireWith: function( context, args ) {
    
                            ...................
                        },
                        fire: function() {
    
                            ................
                        },
                        fired: function() {
    
                            ................
                        }
                    };

    最后Callbacks返回了self对象,所以在self上面定义的仅供Callbacks内部使用。

    内部方法中有2个比较重要,一个是list,所有add添加的函数都存放在list中,另一个是fire方法,它实现了触发list中的函数的具体逻辑。稍后我会重点讲一下它。

    接下来我们分析一下self方法:

    add : 添加函数
    remove : 删除函数
    has : 检测list中是否有相同的函数
    empty : 情空list
    disable : 禁止所有操作 list = stack = memory = undefined
    disabled : list是否可用
    lock : 锁
    locked :锁是否可用
    fireWith : 为触发list中的函数做预处理,最终调用fire方法
    fire : 调用fireWith方法
    fired : fire方法是否运行过

    通过属性的名称不难发现,disabled、locked、fired这3个方法返回的是状态,代码也比较简单,不多做解释相信大家也能看懂。

    disabled: function() {
        return !list;
    },
    locked: function() {
        return !stack;
    },
    fired: function() {
        return !!fired;
    }

    fire方法也比较简单,它调用了fireWith后返回自身,方便链式调用。

    fire: function() {
        self.fireWith( this, arguments );
        return this;
    },

    fireWith方法也不算太复杂,它有2个参数:
    context : 执行上下文
                  我们看fire方法:self.fireWith( this, arguments );
                  是不是就很清楚啦,context就是self对象,args就是arguments
    args : arguments

    然后对args做了处理。把context与args合并在一个数组中。有经验的朋友应该不难看出,这种形式的数组是给apply用的,没错,私有的fire方法中会用到它。

    if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {

    接着往下看,if ( list && ( !fired || stack ) )  这个判断主要是为了once也就是只fire一次。

    fireWith: function( context, args ) {
        args = args || [];
        args = [ context, args.slice ? args.slice() : args ];
        if ( list && ( !fired || stack ) ) {
            if ( firing ) {
                stack.push( args );
            } else {
                fire( args );
            }
        }
        return this;
    },

    我对这个判断语句拆分一下,方便理解,list就不说了,主要说后面2个:
    首次执行fireWith,因为fired是undefined,在这里取反所以为真,确保fire方法至少执行一次,然后在私有fire方法中赋值为true,下一次再执行到这里取反,则为假。

    fire = function( data ) {
        memory = options.memory && data;
        fired = true;
        firingIndex = firingStart || 0;
        firingStart = 0;
    
            ......................

    看到这里相信大家明白了,要实现once必须stack为假才可以。

    stack = !options.once && [],

    没有设置options.once,取反为真,则stack为空数组,否则stack等于false

    接下去又是一个判断:
    if ( firing ) {
              stack.push( args );
    } else {
              fire( args );
    }

    firing是为了避免死循环,当循环内需要执行的函数还没走完,则stack.push( args );

    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;

    当循环走完,检测到stack有长度则再调用fire

    if ( list ) {
        if ( stack ) {
            if ( stack.length ) {
                fire( stack.shift() );
            }
        } else if ( memory ) {
            list = [];
        } else {
            self.disable();
        }
    }

    再看add方法,先是一个函数自执行,接收的参数是arguments,然后遍历arguments,判断如果是函数则list.push(arg),否则调用自己add(arg),由此可以看出,add方法不仅可以传一个函数,还能多个函数逗号隔开,如:cb.add(fn1,fn2);

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

    else if ( memory )  当有记忆功能的时候执行,firingStart = start把循环的起始值设为当前数组的长度值,然后调用fire则只会触发当前添加的函数


    私有方法fire定义了索引值、起始值、长度,就开始循环,如果触发的函数返回false,并且options.stopOnFalse为true,则终止循环。

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

    读了一遍源码后,我们应该对使用Callbacks非常熟悉了:

    //once
    var cb = $.Callbacks('once');
    cb.add(function(){
        alert('a');
    });
    cb.add(function(){
        alert('b');
    });
    cb.fire();//弹出a,b
    cb.fire();//不执行
    
    
    //memory
    var cb = $.Callbacks('memory');
    cb.add(function(){
        alert('a');
    });
    cb.add(function(){
        alert('b');
    });
    cb.fire();//弹出a,b
    
    cb.add(function(){ //弹出c
        alert('c');
    });
    
    
    
    //once memory
    var cb = $.Callbacks('once memory');
    cb.add(function(){
        alert('a');
    });
    cb.add(function(){
        alert('b');
    });
    cb.fire();//弹出a,b
    
    cb.add(function(){ //弹出c
        alert('c');
    });
    cb.fire(); //不执行
    
    
    //add方法多个参数逗号隔开
    var cb = $.Callbacks();
    cb.add(function(){
        alert('a');
    },function(){
        alert('b');
    });
    
    cb.fire(); //弹出a,b
    
    
    //stopOnFalse
    var cb = $.Callbacks('stopOnFalse');
    cb.add(function(){
        alert('a');
        return false;
    },function(){
        alert('b');
    });
    
    cb.fire();//弹出a
    
    
    //lock()
    var cb = $.Callbacks('memory');
    cb.add(function(){
        alert('a');
    });
    
    cb.fire();//弹出a
    cb.lock();//锁住fire()
    
    cb.add(function(){ //弹出b
        alert('b');
    });
    cb.fire();//不执行
    
    
    //remove()
    var cb = $.Callbacks();
    var fn1 = function(){
        alert('a');
    };
    var fn2 = function(){
        alert('b');
    };
    cb.add(fn1);
    cb.add(fn2);
    cb.fire(); //弹出a,b
    
    cb.remove(fn1,fn2);
    cb.fire();//不执行
  • 相关阅读:
    PHP:第四章——PHP数组处理函数
    PHP:第四章——PHP数组array_intersect计算数组交集
    PHP:第四章——PHP数组array_diff计算数组差集
    PHP:第四章——PHP数组查找,替换,过滤,判断相关函数
    GPG入门
    GPG入门教程
    运行gpg --gen-key生成key时出现卡住的问题
    程序员练级攻略(2018) 与我的专栏
    构建一个在线ASCII视频流服务
    Ubuntu 16.04配置国内高速apt-get更新源
  • 原文地址:https://www.cnblogs.com/gongshunkai/p/5927514.html
Copyright © 2011-2022 走看看