视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
jQuery源码之Callbacks的学习
2020-11-27 19:34:01 责编:小采
文档


这篇文章主要介绍了关于jQuery源码之Callbacks的学习,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

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

    下载本文
    显示全文
    专题