天天看點

jQuery動畫子產品源碼分析之queue,dequeue,clearQueue方法源碼分析

問題1:當我們回調隊列中的函數的時候會傳給他什麼參數,他的上下文是什麼

/*通過_queueHooks我們知道,他其中封裝了Callbacks對象,他可以提前使得我們清除元素上面的隊列集合,同時once和memory告訴我們調用fire以後,就會使得Callbacks中封裝的集合變成[],是以多次調用fire是沒有意義的*/
_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
			empty: jQuery.Callbacks("once memory").add(function() {
			jQuery._removeData( elem, type + "queue" );//在queue方法中通過jQuery._data( elem,(type || "fx")+ "queue")儲存
			  jQuery._removeData( elem, key );//promise方法中jQuery._data( elements[ i ], type + "queueHooks" )
			})
		});
	}
hooks = jQuery._queueHooks( elem, type );
	next = function() {
		jQuery.dequeue( elem, type );
	};
fn.call( elem, next, hooks );
           

note:可以看到,在我們的函數隊列執行的時候上下文為調用對象的DOM對象,第一個參數是next可以調用下一個函數,也就是執行dequeue操作;h ooks可以讓我們提前終止後續函數的調用。通過調用hooks.empty.fire來完成!

問題2:當我們不指定隊列名稱,或者指定了隊列名稱但是名稱為"fx"的時候,為什麼會執行調用對象的每一個DOM對象的函數隊列中的第一個函數調用。

if ( type === "fx" && queue[0] !== "inprogress" ) {
	jQuery.dequeue( this, type );
}
           

note:這句代碼告訴我們如果我們指定隊列名稱或者隊列名稱為fx時候就會執行一次dequeue操作,也就是調用函數隊列中的第一個調用。調用後,該函數從回調隊列中移除。

問題3:如果調用queue方法時候隻是傳入了一個字元串參數,那麼為什麼隻會傳回第一個調用對象,也就是this[0]的函數隊列

if ( arguments.length < setter ) {
	return jQuery.queue( this[0], type );
 }
           

note:當隻是傳入一個參數的時候,那麼arguments.length為1,但是setter預設是2,于是隻是傳回第一個調用對象的函數隊列

問題4:如果傳入的參數是undefined,那麼傳回調用對象本身

$("div").queue(undefined)
           

源碼分析之queue方法:

queue: function( type, data ) {
		var setter = 2;//預設參數是兩個
		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
			setter--;
		}//如果隻是傳入一個字元串,那麼表示擷取調用對象第1個DOM的隊列
		if ( arguments.length < setter ) {
			return jQuery.queue( this[0], type );
		}
		return data === undefined ?//如果傳入undefined那麼傳回調用對象
			this :
			this.each(function() {//每一個對象都儲存一個隊列,隊列通過_data方法儲存到DOM對象作為jQuery内部資料
				var queue = jQuery.queue( this, type, data );
				// ensure a hooks for this queue
				jQuery._queueHooks( this, type );//同時每一個對象都維持自己的Callbacks對象,通過這個對象提前結束這個DOM對象的所有回調
				if ( type === "fx" && queue[0] !== "inprogress" ) {
					jQuery.dequeue( this, type );
				}//如果沒有指定隊列名,或者名字為fix,那麼調用每一個DOM對象的第一個回調函數,并且從隊列中移除!
			});
	}
           

note: Callbacks對象,可以讓每一個DOM對象維持的這個函數隊列提前結束,如下面的例子:

$("#div1").queue("fx",[function(next,hooks){
	//next表示執行id為#div1這個元素維持的函數隊列的下一個函數,hooks表示上面的jQuery._queueHooks。如果執行hooks.empty.fire()就能提前結束動畫
		 }])
           

因為執行個體queue方法調用了jQuery.queue方法,我們看看他的源碼:

queue: function( elem, type, data ) {
		var queue;
		if ( elem ) {
			//預設為fixqueue,這裡你就明白了為什麼hooks的empty方法要清除type+"queue"的内部資料
			type = ( type || "fx" ) + "queue";
			//第一次預設是undefined
			queue = jQuery._data( elem, type );//儲存到fixqueue中作為内部資料!
     	             // Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {
				if ( !queue || jQuery.isArray(data) ) {
		//如果沒有擷取到_data方法儲存的資料,那麼我們把函數隊列通過_data方法儲存到DOM上面!如果已經存在,那麼直接插入到原來的集合中!
					queue = jQuery._data( elem, type, jQuery.makeArray(data) );
				} else {
					queue.push( data );
				}
			}
			return queue || [];
		}
	}
           

note:這個方法是可以擷取也是可以儲存的,如果沒有data,也就是調用queue時候沒有傳入第二個參數,那麼直接表示擷取函數隊列, 否則就是把函數隊列儲存到DOM對象的内部資料上!

dequeue方法:

dequeue: function( type ) {
		return this.each(function() {
			jQuery.dequeue( this, type );
		});
	}
           

note:如果是$("div")調用dequeue方法,那麼會為每一個DOM對象調用一次dequeue方法。 注意:調用一次dequeue方法,那麼該回調函數就會從隊列中移除!我們來看看jQuery.dequeue方法:

dequeue: function( elem, type ) {
		type = type || "fx";//首先擷取到該DOM上面的所有的函數隊列
		var queue = jQuery.queue( elem, type ),
			startLength = queue.length,
			fn = queue.shift(),
			hooks = jQuery._queueHooks( elem, type ),
			next = function() {
				jQuery.dequeue( elem, type );
			};
		// If the fx queue is dequeued, always remove the progress sentinel
		if ( fn === "inprogress" ) {//每次顯示調用之前我們必須手動移除上一次添加的字元串"inprogress"
			fn = queue.shift();
			startLength--;
		}
		if ( fn ) {
			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) {//每次顯示調用dequeue以後,我們就會往回調隊列中放入一個字元串"inprogress"
				queue.unshift( "inprogress" );
			}
			// clear up the last queue stop function
			delete hooks.stop;
			fn.call( elem, next, hooks );
		}
		if ( !startLength && hooks ) {//如果startLength為0以後,我們手動銷毀Callbacks中封裝的lists數組!
			hooks.empty.fire();
		}
	}
           

note:每次調用dequeue方法都會把startLength自減,注意:這裡是shift,自減的同時把它從回調隊列中移除!如果自減後為0,那麼就會hooks.empty.fire清除所有的回調隊列,這時候再次調用dequeue已經沒有任何意義了,因為Callbacks内部的list以及是空數組了!

清除回調隊列clearQueue源碼:

clearQueue: function( type ) {
		return this.queue( type || "fx", [] );
	}
           

clearQueue()

函數用于 清空每個比對元素的指定隊列中所有尚未執行的項。底層通過jQuery._data把原來儲存的還沒有執行的函數數組變成空數組!

總結:

(1)隊列的方法還是通過數組配合shift和unshift來完成的,首先為所有的調用對象内部通過jQuery._data方法儲存調用函數隊列,但是如果沒有指定隊列名稱或者隊列名稱是"fx"那麼沒有調用dequeue就會執行隊列中第一個函數。同時我們注意,當調用dequeue的次數已經多于本身函數隊列的長度的時候,多次調用是沒有意義的,因為這時候Callbacks對象中的list已經是空了,是以調用fire函數是多餘的,不會有任何反應。

(2)學習該方法要特别注意每一個函數隊列中的函數回調的時候上下文都是DOM對象,第一個參數是next表示可以調用dequeue方法,而hooks可以幫助我們在程式中手動停止剩下的函數的調用,而不依賴于clearQueue方法!