• jQuery回调、递延对象总结(一)jQuery.Callbacks详解


    前言:
    
    作为参数传递给另一个函数执行的函数我们称为回调函数,那么该回调又是否是异步的呢,何谓异步,如:作为事件处理器,或作为参数传递给
    
    (setTimeout,setInterval)这样的异步函数,或作为ajax发送请求,应用于请求各种状态的处理,我们可以称为异步回调,jQuery.Callbacks
    
    为我们封装了一个回调对象模块,我们先来看一个应用场景:
     
    
    // 为什么jQuery中的ready事件可以执行多个回调,这得益于我们的jQuery.Deferred递延对象(是基于jQuery.Callbacks回调模块)  
    jQuery(function($) {  
        console.log('document is ready!');  
        // do something  
    });  
     
    jQuery(function($) {  
        // do something  
    });  
     
    // 实现原型  
    // jQuery.Deferred版代码  
    var df = jQuery.Deferred();  
    df.resolve(); // 在ready事件中调用  
     
    // 可以多次执行绑定的函数  
    df.done(fn1, fn2, fn3);  
    df.done(fn4);  
    // ...  
     
     
    // jQuery.Callbacks版代码  
    var cb = jQuery.Callbacks('once memory');  
    cb.fire(); // 在ready事件中调用  
     
    // 可以多次执行绑定的函数  
    cb.add(fn1, fn2, fn3);  
    cb.add(fn4);  
    // ... 
    现在我们知道jQuery中的ready事件是可以这样执行多个回调的,要想深入理解其源码,让我们继续看下面吧
    
     
    
    一、jQuery.Callbacks设计思路
    
    使用一个私有变量list(数组)存储回调,执行jQuery.Callbacks函数将返回一个可以操作回调列表list的接口对象,
    而传入jQuery.Callbacks函数的options参数则用来控制返回的回调对象操作回调列表的行为
    
     
    
    回调对象中的方法
     
    
    {  
        add:        增加回调到list中  
        remove:     从list中移除回调  
        fire:       触发list中的回调  
        fired:      回调对象是否执行过fire方法  
        fireWith:   触发list中的回调,第一个参数为执行域  
        has:        判断函数是否在list中  
        empty:      将list致空,list = [];  
        lock:       锁定list  
        locked:     是否锁定  
        disable:    禁用回调对象  
        disabled:   是否禁用  
    } 
    参数标志:
    
    options = {  
        once:       回调对象仅触发(fire)一次  
     
        memory:     跟踪记录每一次传递给fire函数的参数,在回调对象触发后(fired),  
                    将最后一次触发(fire)时的参数(value)传递给在add操作后即将被调用的回调  
     
        unique:     在add操作中,相同的函数仅只一次被添加(push)到回调列表中  
     
        stopOnFalse:当回调函数返回false,中断列表中的回调循环调用,且memory === false,阻止在add操作中将要触发的回调  
    } 
    二、源码解析
    
        var // Used for splitting on whitespace  
            core_rnotwhite = /S+/g;  
         
        var optionsCache = {};  
         
        // Convert String-formatted options into Object-formatted ones and store in cache  
        // 将字符串格式选项转化成对象格式形式,并存储在缓存对象optionsCache[options]中  
        // 该缓存起作用适用于执行多次jQuery.Callbacks函数,且传递options参数一致,我们在jQuery.Deferred  
        // 源码就可以看到tuples二维数组中执行了两次jQuery.Callbacks('once memory')  
        function createOptions( options ) {  
            var object = optionsCache[ options ] = {};  
            jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {  
                object[ flag ] = true;  
            });  
            return object;  
        }  
        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;  
                    // 迭代list回调列表,列表中的回调被应用(或执行回调)  
                    for ( ; list && firingIndex < firingLength; firingIndex++ ) {  
                        // 如果回调返回false,且options.stopOnFlase === true,则中断循环  
                        // 注:data[1]是一个伪数组(self.fire方法中的arguments(参数集合))  
                        if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {                      
                            memory = false; // To prevent further calls using add   // 阻止在add操作中可能执行的回调  
                            break;  
                        }  
                    }  
                    firing = false;  
                    if ( list ) {  
                        // (options.once === undefined),回调对象可以触发多次  
                        if ( stack ) {  
                            // 处理正在执行的回调中的fireWith操作  
                            // 注:如果执行的回调中真的拥有fire或fireWith操作,那么列表中的回调将会无限循环的执行,请看实例1  
                            if ( stack.length ) {  
                                fire( stack.shift() );  
                            }  
                        }  
                        // (options.once === true && options.memory === true)  
                        // 回调列表致空,但允许add继续添加并执行回调  
                        else if ( memory ) {  
                            list = [];  
                        }  
                        // (options.once === true && options.memory === undefined)  
                        // 禁用回调对象  
                        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" ) {  
                                        // 回调不唯一 或 唯一且不存在,则push  
                                        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?  
                            // 如果正在执行的回调执行了add操作,更新firingLength,将列表中新加进来的最后一个回调加入到回调执行的队列中  
                            if ( firing ) {  
                                firingLength = list.length;  
         
                            // With memory, if we're not firing then  
                            // we should call right away  
                            // 如果可能(options.memory === true),在回调对象不能再次fire(options.once === true)时,  
                            // 我们应该使用memory(记录的最后一次fire时的参数)立即调用回调  
                            } 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  
                                    // 在回调对象触发(fire)时,如果firingLength、firingIndex(正在执行的回调在列表list中的索引index)  
                                    // 大于等于 移除的回调的索引(index),分别减一,确保回调执行队列中未执行的回调依次执行  
                                    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  
                    // 将列表致空,list = []; firingLenght = 0;  
                    empty: function() {  
                        list = [];  
                        firingLength = 0;  
                        return this;  
                    },  
                    // Have the list do nothing anymore  
                    // 禁用回调对象  
                    // 将list赋值为undefined就可以使self中的add,remove,fire,fireWith方法停止工作  
                    // 我认为这里把stack、memory赋值为undefined与否是没有任何关系的  
                    disable: function() {  
                        list = stack = memory = undefined;  
                        return this;  
                    },  
                    // Is it disabled?  
                    disabled: function() {  
                        return !list;  
                    },  
                    // Lock the list in its current state  
                    // 锁定回调列表  
                    // 如果(fired !== true || options.memory === false),则视为禁用(disable)  
                    // 如果(fired === true && options.memory === true),则视为options.once === true  
                    // 请看实例2  
                    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 ) {  
                        // 回调对象未执行过fire 或且 可以执行多次(options.once === false)  
                        // 如果(fired === true && options.once === true),则不会执行fire操作  
                        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;  
        }; 
    三、实例
    
    实例1、 处理回调函数中的fire,或fireWidth操作
     
    
        var cb = jQuery.Callbacks();  
         
        var fn1 = function(arg){ console.log( arg + '1' ); };  
        var fn2 = function(arg){ console.log( arg + '2' ); cb.fire();  };  
        var fn3 = function(arg){ console.log( arg + '3' ); };  
         
        cb.add(fn1, fn2, fn3);  
         
        cb.fire('fn'); // 其中回调fn1,fn2,fn3无限制的循环调用  
         
        /*  
        控制台将无限制输出如下:  
        fn1  
        fn2  
        fn3  
        fn1  
        fn2  
        fn3  
        fn1  
        fn2  
        fn3  
        .  
        .  
        .  
        */ 
    
     
    
    实例2、 锁定(lock)操作各种场景中的用法
    
        var cb1 = jQuery.Callbacks();  
        var cb2 = jQuery.Callbacks('memory');  
        var cb3 = jQuery.Callbacks('memory');  
         
        var fn1 = function(arg){ console.log( arg + '1' ); };  
        var fn2 = function(arg){ console.log( arg + '2' ); };  
        var fn3 = function(arg){ console.log( arg + '3' ); };  
         
        // 如果options.memory !== true,锁定操作视为禁用回调对象  
        cb1.add(fn1);  
        cb1.lock();  
        // 以下操作无任何反应  
        cb1.add(fn2);  
        cb1.fire('fn');  
         
        // 如果fried !== true,锁定操作也视为禁用回调对象  
        cb2.add(fn1);  
        cb2.lock();  
        // 以下操作无任何反应  
        cb2.add(fn2);  
        cb2.fire('fn');  
         
        // 如果(fired === true && options.memory === true),锁定操作类似控制标志once(options.once === true);  
        cb3.add(fn1);  
        cb3.fire('fn'); // fn1,此时fired === true  
        cb3.lock();     // 像是传入了'once'标志,jQuery.Callbacks('once memory');  
        cb3.add(fn2);   // fn2  
        cb3.fire('fn'); // 再次触发,无任何反应  
        cb3.add(fn3);   // fn3  
         
         
        // 再来看看jQuery.Deferred中的一段源码  
        var tuples = [  
            // action, add listener, listener list, final state  
            [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],  
            [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],  
            [ "notify", "progress", jQuery.Callbacks("memory") ]  
        ];  
         
        // Handle state  
        if ( stateString ) {  
            list.add(function() {  
                // state = [ resolved | rejected ]  
                state = stateString;  
         
            // [ reject_list | resolve_list ].disable; progress_list.lock  
            }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );  
        }  
         
        /*  
            当执行了tuples中前面两组中任意一个回调对象的fire方法时,后一组回调对象被锁定,  
            相当于(fired === true && options.memory === true),后一组回调对象实际为执行  
            jQuery.Callbacks('once memory')生成的回调对象。  
        */ 
    
    
  • 相关阅读:
    jquery使用--常见前端效果实现
    Quartz —— Spring 环境下的使用
    java设计模式--外观模式(Facade)
    java设计模式--装饰模式(Decorator)
    Java开发中的23种设计模式详解(转)
    java设计模式--工厂模式
    选择排序
    序列化
    解析器
    版本控制
  • 原文地址:https://www.cnblogs.com/chris-oil/p/3810944.html
Copyright © 2020-2023  润新知