乐趣区

jQuery源码解析之queuedequeue和jQueryCallbacks

前言:
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,}


(完)

退出移动版