• jQuery 源码解析(八) 异步队列模块 Callbacks 回调函数详解


    异步队列用于实现异步任务和回调函数的解耦,为ajax模块、队列模块、ready事件提供基础功能,包含三个部分:Query.Callbacks(flags)、jQuery.Deferred(funct)和jQuery.when()。本节讲解Callbacks,也就是回调函数列表

    回调函数用于管理一组回调函数,支持添加、移除、触发、锁定和禁用回调函数,为jQuery.ajax、jQuery.Deferred()和ready()事件提供基础功能,我们也可以基于它编写新的组件。

    使用方法:$.Callbacks(flags),flags是一个参数,用于指定传递的标记,可以为空,可以设置为以下四个选项之一,或者任意组合也可以,如下:

      unique             确保一个回调函数只能被添加一次
      stopOnFlase   当某个回调函数返回false时中断执行
      once                确保回调函数列表只能被触发一次 
      memory           记录上一次触发回调函数列表时的参数,之后添加的任何函数都将用记录的参数值立即调用

    执行成功后返回一个对象,该函数含有如下几个方法:

      add(fn/arr)           添加一个/多个回调函数到list数组中

      remove(fn1,fn2,fn3...)      从list中移除多个回调函数

      empty()          清空list数组

      disable()          禁用列表,使他不再做任何事情,该操作不可还原

      disabled()          判断是否已禁用列表,如果已经禁用了则返回true

      lock()            锁定memory模式下的回调函数的上下文和参数

      locked()             判断回调函数列表是否已被锁定

      fireWith(content,args)    以content为上下文,args为上下文,执行所有函数列表

      fire(arguments)       指定上下文为当前回调函数列表来调用fireWith

      fired()           通过检测变量memory的值来判断回调函数列表是否被触发过

    这些方法归纳起来就是新增/移除/触发回调函数,还有几个是查看当前的状态的(是否已触发、是否被禁用等)

    $.Callbacks()可以传入任意组合,也可以为空,例如:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
    </head>
    <body>
        <script>
            function fn1(val){console.log('fn1 says:' + val);}
            function fn2(val){console.log('fn2 says ' + val);}
            function fn3(val){console.log('fn3 says ' + val);}    
            
            var cbs = $.Callbacks();        //创建Callbacks对象
            cbs.add([fn1,fn2,fn3]);            //添加函数到回调函数列表中
            cbs.fire('test1');                //触发回调函数,参数是test1    输出:fn1 says:test1、fn2 says:test1和fn3 says:test
            cbs.remove(fn1,fn3);            //移除回调函数fn1,fn3
            cbs.fire('test2');                //触发回调函数,输出:fn2 says test2
        </script>
    </body>
    </html>

    输出如下:

    这里我们定义了一个Callback(),没有传入任何参数,比较常用的是once和memory的组合,例如:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
    </head>
    <body>
        <script>
            function fn1(val){console.log('fn1 says:' + val);}
            function fn2(val){console.log('fn2 says ' + val);}
            function fn3(val){console.log('fn3 says ' + val);}
            
            var cbs = $.Callbacks('once memory');
            cbs.add([fn1,fn2]);            
            cbs.fire('test');        //输出:fn1 says:test和fn2 says:test
            cbs.fire('test');        //没有输出,因为设置了once标志,当调用cbs.fire('test')后就把list清空了
            cbs.add(fn3);    
        </script>
    </body>
    </html>

    输出如下:

    如果传入once+memory的组合,这时回调函数被触发后再调用fire()去触发时是不会执行回调函数了的,因为当第一次fire()触发回调函数后,如果由once标记就把内部的list设为了undefined,可以理解为把list给禁用了,加入了memory标记的话当执行fire()时jQuery内部会把当时的上下文和参数保存起来,这样下次直接添加回调函数就会自动回掉函数了

     源码分析


    writer by:大沙漠 QQ:22969969

    $.Callbacks()的实现原理很简单,Callbacks是jQuery内部的一个函数,第一次执行时该函数会把传入的标记给缓存起来,通过作用域保存在内部的一个变量中,之后调用add()添加函数时也会一个个的缓存起来,最后调用fire()触发回调函数时会遍历这些回调函数列表,一个个的去触发,中间根据不同的标记做不同的处理。

    $.Callbacks是直接定义在内部的jQuery上的,大致如下

    jQuery.Callbacks = function( flags ) {                    //在jQuery上添加一个Callbacks方法    
    
        // Convert flags from String-formatted to Object-formatted
        // (we check in cache first)
        flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};            //先尝试从缓存对象flagsCache中获取标记字符串flags对应的标记对象。如果没找到再调用工具函数createFlags()创建标记
    
        var // Actual callback list
            list = [],
            // Stack of fire calls for repeatable lists
            stack = [],
            // Last fire value (for non-forgettable lists)
            memory,
            // 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,
            // Add one or several callbacks to the list
            add = function( args ) {
                /*add 是一个工具函数,用于添加回调函数*/
            },
            // Fire callbacks
            fire = function( context, args ) {
                /*fire也是一个工具函数,用于触发回调函数列表*/
            },
            // Actual Callbacks object
            self = {
                /*对象的定义*/
            };
    
        return self;                                                    //最后返回self,这是一个对象,该对象内定义的属性也就是对外的接口,供我们使用的
    };

    createFlags用于将字符串格式的标记转换为对象格式的标记,如下:

    function createFlags( flags ) {                        //将字符串格式的标记转换为对象格式的标记
        var object = flagsCache[ flags ] = {},                    //初始化object和flagsCache[flags]为空对象
            i, length;
        flags = flags.split( /s+/ );                            //对flags用空格分隔
        for ( i = 0, length = flags.length; i < length; i++ ) {    //遍历每个标记
            object[ flags[i] ] = true;                                //属性值一律设为true,这里访问对象用方括号表示法,可以用变量来表示对象的属性。
        }
        return object;
    }

    以上面的第二个例子为例(once+memory),执行到这里返回后对应的如下:

     然后返回内部的self对象,这样$.Callbacks()就执行完毕了,后面我们调用add添加回调函数时会执行self内的add函数,如下:

    add: function() {                    //添加回调函数
        if ( list ) {
            var length = list.length;
            add( arguments );                                    //用工具函数add添加回调函数
            // 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, unless previous
            // firing was halted (stopOnFalse)
            } else if ( memory && memory !== true ) {
                firingStart = length;
                fire( memory[ 0 ], memory[ 1 ] );
            }
        }
        return this;
    },

    这里红色标记的add是在self对象同作用域的add,这是一个工具函数,如下:

    add = function( args ) {            //添加一个或多个回调函数到数组list中
        var i,
            length,
            elem,
            type,
            actual;
        for ( i = 0, length = args.length; i < length; i++ ) {        //遍历参数
            elem = args[ i ];                                            //第一个参数的内容
            type = jQuery.type( elem );                                    //第一个参数的类型    ;是array或者function
            if ( type === "array" ) {                                    //如果参数是数组
                // Inspect recursively        
                add( elem );                                                //则递归调用自身
            } else if ( type === "function" ) {                            //如果参数是函数
                // Add if not in unique mode and callback is not in
                if ( !flags.unique || !self.has( elem ) ) {                //如果不是unique模式(该模式一个函数只能添加一次,就是回调函数列表中没有重复值),或者 是unique模式但未添加过该函数。
                    list.push( elem );                                        //添加args[i]到数组list中
                }
            }
        }
    },

    最后会push到list中,也就是上一层作用域的list中,例子里执行到这里list中的数据如下:

    这样三个函数都被缓存起来了,最后调用fire()触发回调函数时会执行self内的fire()函数,如下:

    fire: function() {
        self.fireWith( this, arguments );            //指定上下文为当前回调函数列表来调用fireWith(context, args)
        return this;
    }

    fire()会将当前this作为参数,直接调用fireWith,fireWith如下:

    fireWith: function( context, args ) {            //使用指定的上下文和参数触发回调函数列表中的所有回调函数
        if ( stack ) {        
            if ( firing ) {                                    //如果回调函数正在执行当中
                if ( !flags.once ) {            
                    stack.push( [ context, args ] );
                }
            } else if ( !( flags.once && memory ) ) {        //如果回调函数未在执行中,并且不是已经触发过的once模式,
                fire( context, args );                            //调用工具函数fire(context, args )执行所有函数
            }
        }
        return this;
    },

    最后会调用fire,也就是self同作用域的工具fire函数,如下:

    fire = function( context, args ) {            //使用指定的上下文context和参数args调用数组list中的回调函数
        args = args || [];
        memory = !flags.memory || [ context, args ];                //如果当前不是memory模式,则设置memory为ture,表示当前函数回调函数列表被触发过。如果当前回调函数是memory模式,设置momory为[context,args],除了表示当前函数回调函数列表被触发过,还能保存上下文和参数。
        firing = true;                                                //把firing设为ture,表示回调函数正在执行当中
        firingIndex = firingStart || 0;
        firingStart = 0;
        firingLength = list.length;
        for ( ; list && firingIndex < firingLength; firingIndex++ ) {                        //如果没有禁用列表则循环执行每个函数
            if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {    //执行list数组里的每一个函数,如果开启了stopOnFalse标记且有一个回调函数返回false
                memory = true; // Mark as halted                                                    //则把memory设置为ture(清除上下文),且退出余下所有函数列表的执行。
                break;
            }
        }
        firing = false;
        if ( list ) {                                                //如果没有禁用列表
            if ( !flags.once ) {                                        //如果不是once模式,即可多次触发回调函数列表
                if ( stack && stack.length ) {
                    memory = stack.shift();
                    self.fireWith( memory[ 0 ], memory[ 1 ] );
                }
            } else if ( memory === true ) {                                //如果(是once模式,而且不是memory模式) 或者 (是once+memory 且设置了stopOnFlase模式,并且某个回调函数返回了false)
                self.disable();                                                //则禁用回调函数列表
            } else {                                                    //如果是once模式+memory模式
                list = [];                                                    //则清空数组list,后续添加的回调函数还会立即执行。
            }
        }
    },

    fire()执行完后回调函数列表就执行完毕了,中间通过一些标记做处理,如果由传入memory,则会保存上下文,下次通过add添加回调函数时会立即执行的

  • 相关阅读:
    Linux之cd、pwd、mkdir、rmdir
    Linux之目录结构配置
    Linux之chgrp
    Linux之chown
    Linux之chmod
    Linux之用户组、文件权限详解
    Linux命令之shutdown
    Linux命令之man
    Git-.gitignore配置
    Linux内核移植到JZ2440
  • 原文地址:https://www.cnblogs.com/greatdesert/p/11433365.html
Copyright © 2020-2023  润新知