资讯专栏INFORMATION COLUMN

jQuery源码学习之Callbacks

lmxdawn / 3104人阅读

摘要:源码学习之的通过回调实现异步,其实现核心是。回调函数队列中的函数返回时停止触发回调函数队列只能被触发一次记录上一次触发队列传入的值,新添加到队列中的函数使用记录值作为参数,并立即执行。实际是,内部则调用了在定义的局部函数。

jQuery源码学习之Callbacks

jQuery的ajaxdeferred通过回调实现异步,其实现核心是Callbacks

使用方法

使用首先要先新建一个实例对象。创建时可以传入参数flags,表示对回调对象的限制,可选值如下表示。

stopOnFalse:回调函数队列中的函数返回false时停止触发

once:回调函数队列只能被触发一次

memory:记录上一次触发队列传入的值,新添加到队列中的函数使用记录值作为参数,并立即执行。

unique:函数队列中函数都是唯一的

var cb = $.Callbacks("memory");
cb.add(function(val){
    console.log("1: " + val)
})
cb.fire("callback")
cb.add(function(val){
    console.log("2: " + val)
})
// console输出
1: callback
2: callback

Callbacks 提供了一系列实例方法来操作队列和查看回调对象的状态。

add: 添加函数到回调队列中,可以是函数或者函数数组

remove: 从回调队列中删除指定函数

has: 判断回调队列里是否存在某个函数

empty: 清空回调队列

disable: 禁止添加函数和触发队列,清空回调队列和上一个传入的值

disabled: 判断回调对象是否被禁用

lock: 禁用fire,若memory非空则同时add无效

locked: 判断是否调用了lock

fireWith: 传入context和参数,触发队列

fire: 传入参数触发对象,context是回调对象

源码解析

$.Callback()方法内部定义了多个局部变量和方法,用于记录回调对象的状态和函数队列等,返回self,在self实现了上述回调对象的方法,用户只能通过self提供的方法来更改回调对象。这样的好处是保证除了self之外,没有其他修改回调对象的状态和队列的途径。

其中,firingIndex为当前触发函数在队列中的索引,list是回调函数队列,memory记录上次触发的参数,当回调对象实例化时传入memory时会用到,queue保存各个callback执行时的context和传入的参数。self.fire(args)实际是self.fireWith(this,args)self.fireWith内部则调用了在Callbacks定义的局部函数fire

    ...
    // 以下变量和函数 外部无法修改,只能通过self暴露的方法去修改和访问
    var // Flag to know if list is currently firing
        firing,

        // Last fire value for non-forgettable lists
        // 保存上一次触发callback的参数,调用add之后并用该参数触发
        memory,

        // Flag to know if list was already fired
        fired,

        // Flag to prevent firing
        // locked==true fire无效 若memory非空则同时add无效
        locked,

        // Actual callback list
        // callback函数数组
        list = [],

        // Queue of execution data for repeatable lists
        // 保存各个callback执行时的context和传入的参数
        queue = [],

        // Index of currently firing callback (modified by add/remove as needed)
        // 当前正触发callback的索引
        firingIndex = -1,

        // Fire callbacks
        fire = function() {
            ...
        },
        
        // Actual Callbacks object
        self = {
            // Add a callback or a collection of callbacks to the list
            add: function() {
                ...
            },
            ...
            // Call all callbacks with the given context and arguments
            fireWith: function( context, args ) {
                if ( !locked ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ]; // :前为args是数组,:后是string
                    queue.push( args );
                    if ( !firing ) {
                        fire();
                    }
                }
                return this;
            },

            // Call all the callbacks with the given arguments
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },
            ...
        }

通过self.add添加函数到回调队列中,代码如下。先判断是否memory且非正在触发,如果是则将fireIndex移动至回调队列的末尾,并保存memory。接着使用立即执行函数表达式实现add函数,在该函数内遍历传入的参数,进行类型判断后决定是否添加到队列中,如果回调对象有unique标志,则还要判断该函数在队列中是否已存在。如果回调对象有memory标志,添加完毕之后还会触发fire,执行新添加的函数。

            add: function() {
                if ( list ) {

                    // If we have memory from a past run, we should fire after adding
                    // 如果memory非空且非正在触发,在queue中保存memory的值,说明add后要执行fire
                    // 将firingIndex移至list末尾 下一次fire从新add进来的函数开始
                    if ( memory && !firing ) {
                        firingIndex = list.length - 1;
                        queue.push( memory );
                    }

                    ( function add( args ) {
                        jQuery.each( args, function( _, arg ) {
                            // 传参方式为add(fn)或add(fn1,fn2)
                            if ( jQuery.isFunction( arg ) ) {
                                /**
                                 * options.unique==false
                                 * 或
                                 * options.unique==true&&self中没有arg
                                 */
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
                                // 传参方式为add([fn...]) 递归
                                // Inspect recursively
                                add( arg );
                            }
                        } );
                    } )( arguments ); //arguments为参数数组 所以add的第一步是each遍历

                    //添加到list后若memory真则fire,此时firingIndex为回调队列的最后一个函数
                    if ( memory && !firing ) {
                        fire();
                    }
                }
                return this;
            }

firefireWith方法内部实际调用了局部函数fire,其代码如下。触发时,需要更新firedfiring,表示已触发和正在触发。通过for循环执行队里中的函数。结束循环后,将firingIndex更新为-1,表示下次触发从队列中的第一个函数开始。遍历在fireWith中更新过的queuequeue是保存数组的数组,每个数组的第一个元素是context,第二个元素是参数数组。执行函数时要是否返回false且回调对象有stopOnFalse标志,如果是则停止触发。

// Fire callbacks
        fire = function() {

            // Enforce single-firing
            // 执行单次触发
            locked = locked || options.once;

            // Execute callbacks for all pending executions,
            // respecting firingIndex overrides and runtime changes
            // 标记已触发和正在触发
            fired = firing = true;
            // 循环调用list中的回调函数
            // 循环结束之后 firingIndex赋-1 下一次fire从list的第一个开始 除非firingIndex被修改过
            // 若设置了memory,add的时候会修改firingIndex并调用fire
            // queue在fireWith函数内被更新,保存了触发函数的context和参数
            for ( ; queue.length; firingIndex = -1 ) {
                memory = queue.shift();
                while ( ++firingIndex < list.length ) { 

                    // Run callback and check for early termination
                    // memory[0]是content memory[1]是参数
                    if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
                        options.stopOnFalse ) {
                        
                        // Jump to end and forget the data so .add doesn"t re-fire
                        // 当前执行函数范围false且options.stopOnFalse==true 直接跳至list尾 终止循环
                        firingIndex = list.length;
                        memory = false;
                    }
                }
            }

            // 没设置memory时不保留参数
            // 设置了memory时 参数仍保留在其中
            // Forget the data if we"re done with it
            if ( !options.memory ) {
                memory = false;
            }

            firing = false;

            // Clean up if we"re done firing for good
            if ( locked ) {

                // Keep an empty list if we have data for future add calls
                if ( memory ) {
                    list = [];

                // Otherwise, this object is spent
                } else {
                    list = "";
                }
            }
        },

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/96004.html

相关文章

  • jQuery源码习之extend

    摘要:源码学习之用于合并对象,可选择是否深复制。尽管官方文档明确指出第一个参数是的调用情况并不支持,但是这个版本的源码中,判断第一个参数的类型虽有限定是类型,但却未对其值真假加以限定。调用方式源码和指向同一个函数,在函数内部,对调用情况进行区分。 jQuery源码学习之extend $.extend用于合并对象,可选择是否深复制。使用时,第一个参数为合并后的对象。如果要进行深拷贝,则参数1为...

    quietin 评论0 收藏0
  • jQuery源码习之event

    摘要:回调队列中的元素是对象,代表一个事件回调,拥有多个属性,如等等,其中是回调函数,在触发时通过传递,具体的在后面讲。类型是时键表示事件名,规则同上,键值表示事件触发时的回调函数。 jQuery源码学习之event jQuery的事件机制为异步回调,事件监听的属性、参数和回调的等保存在Data实例中,在元素上保存该对象的引用。有方法handle,内部执行dispatch;有属性events...

    XboxYan 评论0 收藏0
  • Java基础习之AJAX技术简单

    摘要:是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。对象是的核心,所有现代浏览器均支持对象和使用。用于在后台与服务器交换数据。及时有效地帮助学员解决疑难问题,提高学员的学习积极性。   Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。  AJAX...

    番茄西红柿 评论0 收藏2637
  • Javascript设计模式习之Decorator(装饰者)模式

    摘要:抽象模式使用的装饰者模式允许我们在运行时或者在随后一个点上动态地将两个或两个以上的对象和它们的属性一起扩展或合并为一个单一对象。定义三个对象目的是为了装饰对象将的额外功能附加到上。 抽象decorator模式 使用jQuery的装饰者模式 jQuery.extend()允许我们在运行时或者在随后一个点上动态地将两个或两个以上的对象(和它们的属性)一起扩展(或合并)为一个单一对象。 定义...

    Joyven 评论0 收藏0
  • jQuery 源码系列(七)Callbacks 函数

    摘要:的支持的方法有几个主要的,和,比如官方有一个例子这两个作为函数调用的生成从基本可以看出,函数生成了一个对象,这个对象的方法是添加回调函数,而方法则是执行回调函数。 欢迎来我的专栏查看系列文章。 讲真,Sizzle 的源码真的太压抑了,以至于写 Sizzle 文章的这段时间里都非常的痛苦,刚开始觉得它还挺有意思的,越到后面越觉得代码很难读懂,烦。 寒假也过完了,在家里待了两周的时间,感觉...

    timger 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<