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

前言:
queue()方法和dequeue()方法是爲 jQuery 的動畫服務的,目的是爲了容許一系列動畫函數被異步調用,但不會阻塞程序。javascript

因此這篇是爲jQuery的動畫解析作準備的。php

1、$.queue()、$.dequeue() 和 $().queue()、$().dequeue() 的區別
(1)$().queue()$().dequeue()
這倆是jQuery.fn.extend()中的方法,也就是供開發者使用的方法,其內部會分別調用 $.queue()$.dequeue()方法。css

//源碼4686行
jQuery.fn.extend( {
  queuefunction( type, data {
    xxx
    return jQuery.queue( this0 ], type )
  },
  dequeuefunction( type, data {
    return jQuery.dequeue( this, type )
  },
})
複製代碼

(2)$.queue()$.dequeue()
這倆是jQuery.extend()中的方法,也就是 jQuery 內部使用的方法。html

  //源碼4594行
  jQuery.extend( {
    queuefunction( elem, type, data {},
    dequeuefunction( elem, type {},
  })
複製代碼

2、$().queue()
做用1:
做爲setter,將function(){}存進特定隊列中。java

<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(){}的數組。web

  /*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( this0 ], 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()方法。緩存

3、jQuery._queueHooks()
做用:
若是目標元素的數據緩存(dataPriv)已存在名稱type+queueHooksHooks話,則直接返回該Hooks,
不然返回有empty屬性的jQuery.Callback()方法生成的對象:閉包

其中的fire()方法用來清除隊列。app

源碼:

    // 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後講解

4、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數組。

5、$().dequeue()
做用:
移出隊頭的函數並執行該callback

源碼:

 jQuery.fn.extend( {
    //出隊
    //移出隊頭的函數並執行它
    //源碼4717行
    dequeue: function( type {
      return this.each( function({
        jQuery.dequeue( this, type );
      } );
    },
})
複製代碼

解析:
其實就是執行$.dequeue()函數。

6、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清除。

7、jQuery.Callbacks()
做用:
jQuerycallbacks回調方法,返回一個object,裏面包含 a、b、c 方法,在執行任意一個方法後,這個方法依舊返回 a、b、c 方法,因此jQuery.Callbacks()是鏈式調用的關鍵函數。

_queueHooks中有用到該函數:

dataPriv.accesselemkey, {
        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
        emptyfunction() {
          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 清空成空數組仍是空字符。

8、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,}


(完)

相關文章
相關標籤/搜索