jQuery源码解析之$.queue()、$.dequeue()和jQuery.Callbacks()

前言:

queue() 方法和 dequeue() 方法是为 jQuery 的动画服务的,目的是为了允许一系列动画函数被异步调用,但不会阻塞程序。

所以这篇是为jQuery的动画解析做准备的。

一、 $ .queue()、 $ .dequeue() 和 $() .queue()、 $() .dequeue() 的区别

(1) $().queue()$().dequeue()

这俩是 jQuery.fn.extend() 中的方法,也就是供开发者使用的方法,其内部会分别调用 $.queue()$.dequeue() 方法。

//源码4686行
jQuery.fn.extend( {
  queue: function( type, data ) {
    xxx
    return jQuery.queue( this[ 0 ], type )
  },
  dequeue: function( type, data ) {
    return jQuery.dequeue( this, type )
  },
})

(2) $.queue()$.dequeue()
这俩是 jQuery.extend() 中的方法,也就是 jQuery 内部使用的方法。

//源码4594行
  jQuery.extend( {
    queue: function( elem, type, data ) {},
    dequeue: function( elem, type ) {},
  })

二、 $ ().queue()

作用1:

作为 setter ,将 function(){} 存进特定队列中。

<div id="A" style="background-color: deeppink">这是A</div>
<script>
  function a() {
    console.log('a','a34')
  }
  function b() {
    console.log('b','b37')
  }
  //将a、b方法存在类型为type的队列里
  //jQuery.fn.queue 给jQuery对象$("A")
  /*setter*/
  $("#A").queue('type', a)
  $("#A").queue('type', b)
</script>

作用2:

作为 getter ,取出特定队列中 function(){} 的数组。

/*getter*/
  $("#A").queue('type') //[a,b]

源码:

jQuery.fn.extend( {
    //入队
    //源码4663行
    //'type', function(){xxx}
    queue: function( type, data ) {
      var setter = 2;

      if ( typeof type !== "string" ) {
        data = type;
        type = "fx";
        setter--;
      }
      //如果参数小于setter,就执行jQuery.queue()方法
      /*这边就是getter*/
      if ( arguments.length < setter ) {
        //this[0] 目标DOM元素
        //type "type"
        //返回[function a(){xxx},function b(){xxx}]
        return jQuery.queue( this[ 0 ], type );
      }
      //如果data是undefined就返回jQuery对象
      return data === undefined ?
        this :
        
        this.each( function() {
          /*这边是setter*/
          var queue = jQuery.queue( this, type, data );
          // Ensure a hooks for this queue
          //确保该队列有一个hooks
          //返回{empty:{
          // 里面是jQuery.Callbacks方法
          // 其中add方法被改写
          // }}
          jQuery._queueHooks( this, type );
          /*专门为fx动画做处理*/
          if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
            jQuery.dequeue( this, type );
          }
        } );
      },

})

解析:

不涉及 fx 动画的话,本质是调用的内部的 jQuery.queue() 方法

(1)如果不足两个参数的话,就调用 jQuery. queue()get 获取数据。

(2)如果大于等于两个参数的话,就调用 jQuery. queue()set 存数据,并且调用 jQuery._queueHooks() ,用来生成一个 queueHooks 对象或者返回当前值。

(3)如果是 fx 动画,并且队头没有 inprogress 锁的话,就执行 jQuery.dequeue() 方法。

三、jQuery._queueHooks()

作用:

如果目标元素的数据缓存( dataPriv )已存在名称 type + queueHooksHooks 话,则直接返回该Hooks,

否则返回有 empty 属性的 jQuery.Callback() 方法生成的对象:

其中的 fire() 方法用来清除队列。

源码:

// Not public - generate a queueHooks object, or return the current one
    //jQuery内部方法,生成一个queueHooks对象或者返回当前值

    //目标元素,"type"
    //源码4676行
    _queueHooks: function( elem, type ) {
      //typequeueHooks
      var key = type + "queueHooks";
      //如果dataPriv已存在名称typequeueHooks的Hooks话,则直接返回该Hooks
      //否则返回有empty属性的jQuery.Callback()方法生成的对象
      return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
        empty: jQuery.Callbacks( "once memory" ).add( function() {
          dataPriv.remove( elem, [ type + "queue", key ] );
        } )
      } );
    }

解析:

jQuery.Callbacks() 方法会放到 $.dequeue 后讲解

四、jQuery.queue()

作用:

callback 依次存入目标元素的 queue 中,或者取出 queue

源码:

jQuery.extend( {
    //作用:目标元素可执行的任务队列
    //源码4596行
    //elem 目标元素
    //$("#A"),"type",function(){xxx}
    queue: function( elem, type, data ) {
      var queue;

      if ( elem ) {
        //typequeue
        type = ( type || "fx" ) + "queue";
        //从数据缓存中获取typequeue队列,如果没有则为undefined
        queue = dataPriv.get( elem, type );
        // Speed up dequeue by getting out quickly if this is just a lookup
        if ( data ) {
          //如果queue不存在,或者data是Array的话
          //就创建queue,queue=[data1,data2,...]
          if ( !queue || Array.isArray( data ) ) {
            queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
          }
          //queue存在的话,就把data push进去
          else {
            queue.push( data );
          }
        }
        //queue=[a,b]
        return queue || [];
      }
    },

})

解析:

(1)作为 setter

function a() {
    console.log('a','a34')
  }

  $("#A").queue('type', a)

此时 data 存在,并且是第一次创建 type='type'queue ,所以使用 dataPriv.access( elem, type, jQuery.makeArray( data ) ) 来创建 queue ,并把 function a push 进 queue 中。

(2)作为 getter

$("#A").queue('type') //[a,b]

此时 data 不存在,直接从数据缓存中获取 queue 并返回。

注意:

jQuery.queue() 始终返回 queue 数组,而 $().queue() 会返回 jQuery 对象或者是 queue 数组。

五、$().dequeue()

作用:

移出队头的函数并执行该 callback

源码:

jQuery.fn.extend( {
    //出队
    //移出队头的函数并执行它
    //源码4717行
    dequeue: function( type ) {
      return this.each( function() {
        jQuery.dequeue( this, type );
      } );
    },
})

解析:

其实就是执行 $.dequeue() 函数。

六、jQuery.dequeue()

作用:

同五。

源码:

jQuery.extend( {
    //源码4624行
    //目标元素,'type'
    dequeue: function( elem, type ) {
      //'type'
      type = type || "fx";
      //get,获取目标元素的队列
      var queue = jQuery.queue( elem, type ),
        //长度
        startLength = queue.length,
        //去除对首元素,并返回该元素
        fn = queue.shift(),
        //确保该队列有一个hooks
        hooks = jQuery._queueHooks( elem, type ),
        //next相当于dequeue的触发器
        next = function() {
          jQuery.dequeue( elem, type );
        };

      // If the fx queue is dequeued, always remove the progress sentinel
      //如果fn='inprogress',说明是fx动画队列正在出队,就移除inprogress
      if ( fn === "inprogress" ) {
        fn = queue.shift();
        startLength--;
      }

      if ( fn ) {

        // Add a progress sentinel to prevent the fx queue from being
        // automatically dequeued
        //如果是fx动画队列的话,就添加inprogress标志,来防止自动出队执行
        //意思应该是等上一个动画执行完毕后,再执行下一个动画
        if ( type === "fx" ) {
          queue.unshift( "inprogress" );
        }

        // Clear up the last queue stop function
        //删除hooks的stop属性方法
        delete hooks.stop;
        //递归dequeue方法
        fn.call( elem, next, hooks );
      }
      console.log(startLength,'startLength4669')
      //如果队列是一个空数组,并且hooks存在的话,清除该队列
      if ( !startLength && hooks ) {
        console.log('aaaa','bbbb4671')
        //进行队列清理
        hooks.empty.fire();
      }
    },

  })

解析:

(1) inprogress 应该是一个锁,当 fx 动画执行动画 A 的时候,就加锁,当动画 A 执行完毕后,就解锁,再去运行下一个动画。

(2)注意下 fn.call( elem, next, hooks ) ,保持 fnthiselement 的同时,给 fn 传的两个参数,分别为 nexthooks ,方便操作。

(3)当 queue 是空数组的时候,就触发 hooks.emptyfire() 方法,将 queue 清除。

七、jQuery.Callbacks()

作用:

jQuerycallbacks 回调方法,返回一个object,里面包含 a、b、c 方法,在执行任意一个方法后,这个方法依旧返回 a、b、c 方法,所以 jQuery.Callbacks() 是链式调用的关键函数。

_queueHooks 中有用到该函数:

dataPriv.access( elem, key, {
        empty: jQuery.Callbacks( "once memory" ).add( function() {
          dataPriv.remove( elem, [ type + "queue", key ] );
        } )
      } );

源码:

/*创建一个使用以下参数的callback列表
 * Create a callback list using the following parameters:
 *  options:是一个可选的空格分开的参数,它可以改变callback列表的行为或形成新的option对象
 *    options: an optional list of space-separated options that will change how
 *            the callback list behaves or a more traditional option object
 * 默认情况下一个回调列表会表现成一个event callback列表并且会触发多次
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 * option可选值:
 * Possible options:
 *  确保callback列表只会被触发一次,比如Deferred对象
 *    once:            will ensure the callback list can only be fired once (like a Deferred)
 *  保持跟踪之前的values,并且会在list用最新的values触发后,调用该回调函数
 *    memory:            will keep track of previous values and will call any callback added
 *                    after the list has been fired right away with the latest "memorized"
 *                    values (like a Deferred)
 *  //确保callback只会被添加一次
 *    unique:            will ensure a callback can only be added once (no duplicate in the list)
 *  //当callbak返回false时打断调用
 *    stopOnFalse:    interrupt callings when a callback returns false
 *
 */
  //源码3407行
  //callbacks回调对象,函数的统一管理
  //once memory
  jQuery.Callbacks = function( options ) {

    // Convert options from String-formatted to Object-formatted if needed
    // (we check in cache first)
    options = typeof options === "string" ?
      //options: {once:true,memory:true}
      createOptions( options ) :
      jQuery.extend( {}, options );

    //用来知道list是否正被调用
    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,

      // Flag to prevent firing
      locked,

      // Actual callback list
      list = [],

      // Queue of execution data for repeatable lists
      queue = [],

      // Index of currently firing callback (modified by add/remove as needed)
      firingIndex = -1,
      //触发list中的回调函数
      // Fire callbacks
      fire = function() {
        //true
        // Enforce single-firing
        //'once memory'中的'once'只允许触发一次
        locked = locked || options.once;

        // Execute callbacks for all pending executions,
        // respecting firingIndex overrides and runtime changes
        fired = firing = true;
        for ( ; queue.length; firingIndex = -1 ) {
          //从queue移除第一个元素,并返回该元素
          memory = queue.shift();
          while ( ++firingIndex < list.length ) {

            // Run callback and check for early termination
            //memory=[document, Array(1)]
            //memory[0]是document
            //意思就是让document去执行add()方法中添加的callback函数
            if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
              options.stopOnFalse ) {

              // Jump to end and forget the data so .add doesn't re-fire
              firingIndex = list.length;
              memory = false;
            }
          }
        }

        // 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
        //如果once:true,清空list数组
        if ( locked ) {

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

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

      // Actual Callbacks object
      self = {
        //添加一个回调函数或者是一个回调函数的集合
        // Add a callback or a collection of callbacks to the list
        add: function() {
          if ( list ) {

            // If we have memory from a past run, we should fire after adding
            if ( memory && !firing ) {
              firingIndex = list.length - 1;
              queue.push( memory );
            }
            //闭包
            //将arguments作为参数即args传入闭包的add方法中
            ( function add( args ) {
              //args[0]即function(){dataPriv.remove( elem, [ type + "queue", key ] ) }
              jQuery.each( args, function( _, arg ) {
                if ( isFunction( arg ) ) {
                  //如果self对象没有该方法,将其push进list中
                  if ( !options.unique || !self.has( arg ) ) {
                    list.push( arg );
                  }
                } else if ( arg && arg.length && toType( arg ) !== "string" ) {

                  // Inspect recursively
                  add( arg );
                }
              } );
            } )( arguments );
            //undefined undefined
            if ( memory && !firing ) {
              fire();
            }
          }
          //this即self对象
          //也就说在调用self对象内的方法后会返回self对象本身
          return this;
        },

        // Remove a callback from the list
        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;
        },

        // 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.length > 0;
        },

        // Remove all callbacks from the list
        empty: function() {
          if ( list ) {
            list = [];
          }
          return this;
        },

        // Disable .fire and .add
        // Abort any current/pending executions
        // Clear all callbacks and values
        disable: function() {
          locked = queue = [];
          list = memory = "";
          return this;
        },
        disabled: function() {
          return !list;
        },

        // Disable .fire
        // Also disable .add unless we have memory (since it would have no effect)
        // Abort any pending executions
        lock: function() {
          locked = queue = [];
          if ( !memory && !firing ) {
            list = memory = "";
          }
          return this;
        },
        locked: function() {
          return !!locked;
        },

        // Call all callbacks with the given context and arguments
        fireWith: function( context, args ) {
          if ( !locked ) {
            args = args || [];
            args = [ context, args.slice ? args.slice() : args ];
            queue.push( args );
            if ( !firing ) {
              fire();
            }
          }
          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;
        }
      };
    console.log(queue,'queue3614')
    return self;
  };

解析:

主要看 add()fire() 方法

(1) self.add()

注意里面的闭包函数,使用闭包的目的是 冻结args的值 ,这样可以避免异步调用造成的值得改变。

add()方法就是将 function() {dataPriv.remove( elem, [ type + "queue", key ] );} push 进 list 数组中,以供 fire() 来调用 list 中的callback。

注意最后返回的是 this ,即 self 对象,也就说在调用 self 对象内的方法后会返回 self 对象本身,而 self 内部又含有 add()、fire() 等方法,通过 jQuery.Callbacks 传入的参数 options 来控制能否调用,及调用的次数。

(2) self.fire()
作用是触发 list 中的回调函数, onece memoryonce 表示只让 fire() 触发一次后,就需要清理 list, memory 表示是将 list 清空成空数组还是空字符。

八、createOptions()

作用:

将特定格式的string(空格分开),转化为特定格式的 object({xxx:true,xxx:true,...} .

源码:

//将特定格式的string(空格分开),转化为特定格式的object({xxx:true,xxx:true,...})
// Convert String-formatted options into Object-formatted ones
  //源码3377行
  //'once memory' —> {once:true,memory:true}
  function createOptions( options ) {
    var object = {};
    jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
      object[ flag ] = true;
    } );
    return object;
  }

解析:

将以空格连接的字符串,以空格拆开,并作为 object 的key,其 value 为 true

比如:

"once memory" => {once:true,memory:true,}

(完)

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章