我們先來看一下jQuery中有關隊列操作的方法集:
從上圖可以看出,既有靜態方法,又有實例方法。queue方法,相當於數組中的push操作。dequeue相當於數組的shift操作。舉個例子:
function aaa(){
alert(1);
}
function bbb(){
alert(2);
}
$.queue(document,"q1",aaa); //在document下創建一個隊列q1,並往q1隊列中添加aaa函數。
$.queue(document,"q1",bbb); //在document下取隊列q1,並且往q1隊列中添加bbb函數。
$.queue(document,"q1",[aaa,bbb]); //跟上面兩行代碼的效果一樣,一次性添加多個函數
console.log($.queue(document,"q1")) //打印出,document下的隊列q1的值,結果是:[aaa(),bbb()]
隊列中存儲的必須是函數。
$.dequeue(document,"q1") //取出隊列中的第一項,這里是aaa,然后執行aaa函數,彈出1.
$.dequeue(document,"q1") //取出隊列中的第一項,因為aaa已經出隊了,所以這時的第一項是bbb,因此執行bbb函數,彈出2。
以上調用的是靜態方法,那么實例方法如何調用呢?
$(document).queue("q1",aaa); //入隊
$(document).queue("q1",bbb); //入隊
$(document).dequeue("q1"); //出隊,執行aaa函數。
$(document).dequeue("q1"); //出隊執行bbb函數
隊列在前端開發中有什么作用呢?請看下面這個例子:
div1的寬度和高度是100,left是0.
$("#div1").click(function(){
$(this).animate({width:300},2000); //通過setInterval來實現,在兩秒中,把div1的寬度從100變成300
$(this).animate({height:300},2000); //以此類推
$(this).animate({left:300},2000); //以此類推
})
當點擊div1這個元素時,這個元素會先把寬度變成300px(時間2秒),然后再把高度變成300px(時間2秒),最后把left變成300px(時間2秒)。大家知道為什么會這樣了,因為animate方法其實就是一個入隊和出隊操作,入隊操作:先把寬度從100到300的函數入隊,接着把高度從100到300的函數入隊,最后把left從0到300的函數入隊。出隊操作:先執行隊列中的第一個函數,等第一函數執行結束后(把寬度從100變成300),再出隊執行第二個函數(把高度從100到300)。以此類推。
隊列queue跟Deferred延遲對象有什么區別?Deferred針對一個異步或者簡單的異步進行操作,而queue可以針對多個異步進行操作,就比如上面這個例子,隊列里面的每一項就是一個異步操作。這種多異步的操作,用Deferred實現很復雜,不可用。當然隊列也可以處理同步操作。
另外再舉一個例子:
$("#div1").click(function(){
$(this).animate({width:300},2000).animate({left:300},2000);
})
鏈式操作,也是先把寬度從100變成300之后,再把left從0變成300.
但是$(this).animate({width:300},2000).queue("fx",function(){}).animate({left:300},2000);
只會把寬度從100變成300,然后就不動了。為什么呢?因為queue方法只是入隊沒有出隊,當往"fx"隊列中添加了一個空函數時,由於"fx"是animate隊列的默認值,所以這時的fx隊列有三個函數,當第一個函數執行完之后執行出隊操作,這時這個空函數執行,執行完后,沒有進行出隊操作,所以它后面的函數(把left從0變成300)不會出隊執行。
因此我們可以在空函數中進行出隊操作$(this).animate({width:300},2000).queue("fx",function(){$(this).dequeue("fx")}).animate({left:300},2000); 這時,把left從0變成300的函數就可以執行了。由於fx是隊列中的默認名字,因此上面的代碼,可以不用寫fx,也就是可以這樣寫:$(this).animate({width:300},2000).queue(function(){$(this).dequeue()}).animate({left:300},2000);當然也可以這樣寫:$(this).animate({width:300},2000).queue(function(){next()}).animate({left:300},2000);next方法也是出隊的意思。
animate方法還有第三個參數,就是回調方法,比如:
$(this).animate({width:300},2000,function(){}).animate({left:300},2000);當把div的寬度從100變成300時(2秒),就會執行function(){},在執行function里面的代碼后,就會立即執行把left從0到300的函數。如果function(){}里面有一個定時器,比如:把高度從100變成300的定時器函數。這時就會出現高度和left同時變化的情況。
由此可知animate的回調方法,不可控,意思就是它的回調方法執行后會立即執行后面的animate中添加的定時器函數。但是queue就不一樣,它是可控的,我們可以等function函數執行完成后(也就是說把function里面的把高度從100變成300的定時器函數執行結束后),再調用next方法,這時它后面的animate添加的定時器函數才會執行,也就是進行出隊,執行把left從0變成300的定時器函數。能很方便的控制異步操作的流程。
接下來我們來看源碼實現:
jQuery.extend({
queue: function( elem, type, data ) {
var queue;
if ( elem ) { //當有元素時,才進行操作。比如:$.queue(document,"q1",aaa);document就是elem
type = ( type || "fx" ) + "queue"; //如果沒有傳入q1那么type默認為fx,加上queue字符串
queue = data_priv.get( elem, type ); //去數據緩存中獲取此元素的q1queue屬性值,第一次時,是undefined。
if ( data ) { //data就是aaa
if ( !queue || jQuery.isArray( data ) ) { //如果取的值不存在,第一次時是不存在的。進入if語句
queue = data_priv.access( elem, type, jQuery.makeArray(data) );//把data也就是aaa函數轉換成數組形式,也就是變成[aaa()]
} else { //當第二次執行時,也就是$.queue(document,"q1",bbb); 因為里面已經有[aaa()]了,因此直接把data(這里是bbb函數)push到q1queue屬性值中。當然這里有一個例外,比如:第二次執行時,是這樣的$.queue(document,"q1",[bbb]);傳入的data是個數組,這時不會走這里,而是走if語句,這樣的話,會把之前的[aaa()]覆蓋掉,只會有[bbb()]。
queue.push( data );
}
}
return queue || []; //返回這個隊列,其實就是這個數組[aaa(),bbb()]
}
},
dequeue: function( elem, type ) { //$.dequeue(document,"q1")
type = type || "fx";
var queue = jQuery.queue( elem, type ), //先獲取這個q1隊列的值
startLength = queue.length,
fn = queue.shift(), //取隊列中的第一項
hooks = jQuery._queueHooks( elem, type ), //hooks其實是元素elem在數據緩存中的一個屬性對象,如果我們調用的是$.dequeue(document,"q1") 的話,那么屬性對象名就是q1queueHooks,屬性值是{empty: jQuery.Callbacks("once memory").add(function() { data_priv.remove( elem, [ type + "queue", key ] );})}。因此你使用hooks.empty,其實就是q1queueHooks.empty。
next = function() { //這個next方法其實就是出隊
jQuery.dequeue( elem, type );
};
if ( fn === "inprogress" ) { //這里為什么會出現inprogress呢?舉個例子:$(this).animate({width:300},2000,function(){}).animate({left:300},2000);這個代碼的意思是入隊兩個定時器函數,第一個定時器函數是把寬度從100變成300,第二個定時器函數是把left從0變成300.如果這里只有入隊操作,沒有出隊操作,那么這兩個定時器函數都不會執行,因此大家可以去看queue的實例方法,源碼在下面,里面有這樣一個判斷:if ( type === "fx" && queue[0] !== "inprogress" ) {jQuery.dequeue( this, type );},animate的入隊,默認隊列為fx,而且它的隊列的第一項不是inprogress,而是第一個定時器函數,這時進入if語句,進行出隊。因此才能執行第一個定時器函數。那么第二個定時器函數來入隊時,也會馬上出隊嗎?不會,不然的話,兩個定時器函數會同時執行了。那么第二個定時器函數為什么沒有立馬出隊,是因為第一個定時器函數出隊時,會在fx隊列前面添加inprogress,因此第二個定時器函數入隊時,fx隊列的第一個項就是inprogress,因而不會進行出隊操作。 第一個定時器函數執行完之后,就會進行再次出隊,這時第二個定時器函數就會執行了。
fn = queue.shift(); //如果取出的隊列的第一項是inprogress,這時隊列是[bbb()],因為inprogress已經出隊了,就再次出隊,這時bbb()出隊,隊列為[],fn為bbb。
startLength--; //startLength變成1
}
if ( fn ) { //當數組為["inprogress"]出隊時,fn = undefined,startLength=0;這時就會結束隊列操作了。
if ( type === "fx" ) { //當是默認隊列時,也就是animate操作時,就會先往隊列的前面添加inprogress
queue.unshift( "inprogress" ); //隊列變成 ["inprogress"],這時就會執行bbb(),執行完之后,又出隊。
}
delete hooks.stop;
fn.call( elem, next, hooks ); //這里就會執行第一個定時器函數,執行完之后,就會調用next方法,進行出隊。這時的隊列是["inprogress",bbb()]
}
if ( !startLength && hooks ) { //當隊列結束后,清理數據緩存中隊列數據
hooks.empty.fire(); //這里執行fire方法,就會觸發add添加的方法,也就是data_priv.remove( elem, [ type + "queue", key ] );把緩存數據中的所有隊列信息,以及q1queueHooks一起刪除掉。
}
},
_queueHooks: function( elem, type ) {
var key = type + "queueHooks";
return data_priv.get( elem, key ) || data_priv.access( elem, key, {
empty: jQuery.Callbacks("once memory").add(function() { //empty的屬性值是一個Callbacks對象,Callbacks的特點是可以通過它的add方法添加函數,當調用Callbacks的fire方法時,就會執行add添加的方法。
data_priv.remove( elem, [ type + "queue", key ] );
})
});
}
});
jQuery.fn.extend({
queue: function( type, data ) { // $(document).queue("q1",aaa);
var setter = 2;
if ( typeof type !== "string" ) { //當type不等於字符串時,也就是這種情況時:$(document).queue(aaa);
data = type; //data = aaa,type = "fx",setter =1
type = "fx";
setter--;
}
if ( arguments.length < setter ) { //這里判斷是獲取,還是設置。比如:$(document).queue("q1"),這里是獲取操作,因此進入if語句。
return jQuery.queue( this[0], type ); //獲取是針對一組元素的第一個元素。
}
return data === undefined ? this : this.each(function() { //這里就是設置操作,對每個元素都進行設置
var queue = jQuery.queue( this, type, data ); //入隊操作,會在緩存系統中添加一個隊列q1,隊列中,入隊aaa。
jQuery._queueHooks( this, type ); //設置元素的hooks對象,會在緩存系統中添加一個hooks屬性,它可以移除緩存系統中與元素this,相關的隊列操作的所有數據。
if ( type === "fx" && queue[0] !== "inprogress" ) { //跟靜態方法的queue的思路一樣。
jQuery.dequeue( this, type );
}
});
},
dequeue: function( type ) { //$(document).dequeue("q1"); 出隊操作,是針對一組元素的。也就是說如果有多個document被匹配上,那么會對每個document都做出隊操作。
return this.each(function() {
jQuery.dequeue( this, type );
});
},
delay: function( time, type ) { //第一個參數是延遲的時間,第二個參數是哪個隊列(隊列的名字)延遲,我們先來舉個例子說下delay方法的作用:$(this).animate({width:300},2000).delay(2000).animate({left:300},2000);這個代碼的意思是:第一個定時器函數執行結束后,會延遲兩秒鍾,才會執行第二個定時器函數。
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; //jQuery.fx.speeds = {slow: 600,fast: 200,_default: 400};意思就是說,你delay里面是否寫了"slow","fast",或"_default"。如果是,就直接調用默認的值,如果傳入的是數字,那么就只用數字。
type = type || "fx";
return this.queue( type, function( next, hooks ) {
var timeout = setTimeout( next, time ); //延遲time秒,再進行出隊。意思就是time秒后,第二個定時器函數才會執行。
hooks.stop = function() { //這個方法會清除定時器,如果執行,next方法就不會執行,也就不會出隊了
clearTimeout( timeout );
};
});
},
clearQueue: function( type ) {
return this.queue( type || "fx", [] ); //把隊列變成空數組,上面說到,如果傳入數組,會覆蓋隊列的原數組
},
promise: function( type, obj ) { //type是指隊列的名字,如果此type的隊列全部出隊后,就會執行done添加的方法。我們先舉個例子說下這個方法的作用:$(this).animate({width:300},2000).animate({left:300},2000);$(this).promise().done(function(){alert(3)});這句代碼的意思是,等上面兩個定時器函數都執行結束后(因為他們默認處理的都是fx隊列)。才會執行彈出3的函數。
var tmp,
count = 1,
defer = jQuery.Deferred(), //新建一個延遲對象
elements = this,
i = this.length, //元素的個數,這里假設是一個document元素
resolve = function() {
if ( !( --count ) ) {
defer.resolveWith( elements, [ elements ] );
}
};
if ( typeof type !== "string" ) { //如果沒傳入隊列名,就用fx默認隊列
obj = type;
type = undefined;
}
type = type || "fx";
while( i-- ) { //執行一次
tmp = data_priv.get( elements[ i ], type + "queueHooks" ); //去緩存系統找跟這個元素有關的數據
if ( tmp && tmp.empty ) { //如果存在,就證明隊列中有定時器函數要執行。進入if語句
count++; //count等於2
tmp.empty.add( resolve ); //當調用tmp.empty.fire方法時,就會執行resolve 方法。而這里會等fx類型的隊列全部出隊后(這兩個定時器函數都執行結束后),才會觸發fire方法,這時就會執行add添加的所有方法,resolve就是其中一個,於是count就會變成0(在出隊列時,下面的resolve方法已經執行一次了),進入if語句,執行延遲對象的resolveWith,而此方法,就會觸發延遲對象的done方法添加的函數,因此彈出3的函數執行。
}
}
resolve(); //這里會先執行一次resolve方法,count--,變成1。
return defer.promise( obj ); //返回這個延遲對象。
}
});
這一課的知識點牽涉到前面的Callbacks,Deferred等方法,因此沒看懂這兩個東西,這個queue當然有看不懂。因此騷年們,加油吧!
加油!