jquery3.0源码解读(四)Callbacks

本文详细介绍了jQuery 3.0中Callbacks模块,它用于管理回调函数列表,支持如once、memory、unique和stopOnFalse等选项。Callbacks提供了添加、禁用、删除回调等功能,常用于事件处理和复杂场景下的回调执行控制。文章通过源码分析,揭示了Callbacks的工作原理及其在多线程限制下的回调执行策略。
摘要由CSDN通过智能技术生成

本来想先分析defer的,但是在defer的文件中,发现它依赖于另外一个比较重要的模块callbacks。于是,这节就先分析callbacks吧。

前置知识

  • 观察者模式
  • 钩子
  • 递延

应用场景

在js开发中,由于没有多线程,经常会遇到回调这个概念,比如说,在 ready 函数中注册回调函数,注册元素的事件处理等等。在比较复杂的场景下,当一个事件发生的时候,可能需要同时执行多个回调方法,可以直接考虑到的实现就是实现一个队列,将所有事件触发时需要回调的函数都加入到这个队列中保存起来,当事件触发的时候,从这个队列中依次取出保存的函数并执行。

功能说明

callbacks指一个多用途的回调列表对象,提供了强大的的方式来管理回调函数列表。简单的使用方式如下:

function fn1( value ){
    console.log( value );
}

function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}

var callbacks = $.Callbacks();
callbacks.add( fn1 );
callbacks.fire( "foo!" ); // outputs: foo!

callbacks.add( fn2 );
callbacks.fire( "bar!" ); // outputs: bar!, fn2 says: bar!

简单来说就是管理回调函数的执行的。这个模块主要还是提供给jquery内部的ajax和defer使用。

$.callbacks(flags)

$.callbacks(flags)中的flags是$.Callbacks()的一个可选参数, 结构为一个用空格标记分隔的标志可选列表,用来改变回调列表中的行为 (比如. $.Callbacks( ‘unique stopOnFalse’ ))。

可用的 flags:

  • once: 确保这个回调列表只执行一次
  • memory: 当队列已经触发之后,再添加进来的函数就会直接被调用(并且使用的是上一次fire的参数),不需要再触发一次。
  • unique: 保证函数的唯一。
  • stopOnFalse: 当一个回调返回false 时中断调用

方法说明

  1. callbacks.add() 回调列表中添加一个回调或回调的集合。
  2. callbacks.disable() 禁用回调列表中的回调
  3. callbacks.disabled() 确定回调列表是否已被禁用。
  4. callbacks.empty() 从列表中删除所有的回调.
  5. callbacks.fire() 用给定的参数调用所有的回调
  6. callbacks.fired() 访问给定的上下文和参数列表中的所有回调。
  7. callbacks.fireWith() 访问给定的上下文和参数列表中的所有回调。
  8. callbacks.has() 确定列表中是否提供一个回调
  9. callbacks.lock() 锁定当前状态的回调列表。
  10. callbacks.locked() 确定回调列表是否已被锁定。
  11. callbacks.remove() 从回调列表中的删除一个回调或回调集合。

源码分析

源码如下:

jQuery.Callbacks = function( options ) {

    options = typeof options === "string" ?
        createOptions( options ) :
        jQuery.extend( {}, options );

    var firing,

        memory,

        fired,

        locked,

        list = [],

        queue = [],

        firingIndex = -1,

        fire = function() {

            locked = options.once;

            fired = firing = true;
            for ( ; queue.length; firingIndex = -1 ) {
                memory = queue.shift();
                while ( ++firingIndex < list.length ) {

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

                        firingIndex = list.length;
                        memory = false;
                    }
                }
            }

            if ( !options.memory ) {
                memory = false;
            }

            firing = false;

            if ( locked ) {

                if ( memory ) {
                    list = [];

                } else {
                    list = "";
                }
            }
        },

        self = {

            add: function() {
                if ( list ) {

                    if ( memory && !firing ) {
                        firingIndex = list.length - 1;
                        queue.push( memory );
                    }

                    ( function add( args ) {
                        jQuery.each( args, function( _, arg ) {
                            if ( jQuery.isFunction( arg ) ) {
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {

                                // Inspect recursively
                                add( arg );
                            }
                        } );
                    } )( arguments );

                    if ( memory && !firing ) {
                        fire();
                    }
                }
                return this;
            },

            remove: function() {
                jQuery.each( arguments, function( _, arg ) {
                    var index;
                    while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                        list.splice( index, 1 );

                        // Handle firing indexes
                        if ( index <= firingIndex ) {
                            firingIndex--;
                        }
                    }
                } );
                return this;
            },

            has: function( fn ) {
                return fn ?
                    jQuery.inArray( fn, list ) > -1 :
                    list.length > 0;
            },

            empty: function() {
                if ( list ) {
                    list = [];
                }
                return this;
            },

            disable: function() {
                locked = queue = [];
                list = memory = "";
                return this;
            },
            disabled: function() {
                return !list;
            },

            lock: function() {
                locked = queue = [];
                if ( !memory && !firing ) {
                    list = memory = "";
                }
                return this;
            },
            locked: function() {
                return !!locked;
            },

            fireWith: function( context, args ) {
                if ( !locked ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    queue.push( args );
                    if ( !firing ) {
                        fire();
                    }
                }
                return this;
            },

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

            fired: function() {
                return !!fired;
            }
        };

    return self;
};

下面,我们就直接从构造方法和返回self中提供的操作方法,按照上面的操作方法列表顺序依次分析。

$.callbacks(flags)

取出了options(flag)并保存,并定义了若干参数,分别是:

  • firing:是否正在fire触发阶段,用来判断是外部的触发,还是回调函数内部的嵌套触发
  • memory:记录上次触发时使用的参数(相当于执行环境)
  • fired:记录是否已经被触发过至少一次
  • locked:锁定外部fire相关接口
  • list:回调列表
  • queue:多次fire调用(因为可能被嵌套调用)的调用参数列表(相当于执行环境列表)
  • firingIndex:回调列表list的触发索引,也会用在指定add递延触发位置

还有一个fire内部方法,这个比较重要,它的执行过程如下:

首先,locked = options.once;也就是说,如果flag中有once选项,那么只要执行过fire,下一次就会锁住。

接着,fired = firing = true;标示正在执行fire。

之后,循环从queue中取出执行执行环境,赋值给memory(保存了当前执行环境)。并不断顺序执行列表中的回调函数。期间还判断了stopOnFalse选项。当回调返回false时,并且有stopOnFalse选项,就会销毁memory,相当于memory选项没有了。

再接着,判断options.memory是否定义,如果没有,同样销毁memory(上一次的执行环境)。

最后,判断锁定,如果锁定,外部fire就不用了,由是否有递延指定add(会调用内部fire)是否可用,无递延就要disable掉(locked+list)。

最后,返回self,self中暴露了各种操作方法,如下。

callbacks.add()

首先,判断list(回调函数列表),实现了once功能。在上述fire函数中,如果有once标志,list将被赋值成空。

接着,如果有memory(记录着上次触发时使用的参数),并且不在执行的话,保存firingIndex(回调列表list的触发索引,也会用在指定add递延触发位置),并且queue(多次fire调用(因为可能被嵌套调用)的调用参数列表)存入memory。

接着,把add的回调函数,压入list,这里支持递归add。

最后,如果不在firing中,并且是memory模式,则执行fire,由于之前queue中压入了memory,并记录了firingIndex,所以fire会执行刚刚被add进list的回调函数。如果在firing中,当前的函数,自然也会被执行。

callbacks.disable()

这个比较简单,把locked ,queue,list,memory都清空,add将无法使用,fire同样不行。

callbacks.disabled()

直接返回!list。

callbacks.empty()

清空list 。

callbacks.fire()

直接调用了fireWith,并传入了参数。

callbacks.fired()

返回!!fired(双感叹号指强制转换成布尔值)。

callbacks.fireWith()

判断是否locked,如果不是,把参数格式化成[环境,参数数组],写进queue。并执行fire。

callbacks.has()

判断是否有指定回调,无参数则判断回调列表是否空。

callbacks.lock()

无递延(每次执行完memory重置为false)或没触发过,则直接禁用;否则还是可以相应add后的递延函数。

callbacks.locked()

返回!!locked。

callbacks.remove()

移除回调,支持多参数。去掉所有相同回调,当回调内调用remove时,若删除项为已执行项,修正了firingIndex位置。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值