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

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()方法生成的对象:

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

其中的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,}


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

(完)

相关推荐