Queue隊列,如同data數據緩存與Deferred異步模型一樣,都是jQuery庫的內部實現的基礎設施
Queue隊列是animate動畫依賴的基礎設施,整個jQuery中隊列僅供給動畫使用
Queue隊列
隊列是一種特殊的線性表,只允許在表的前端(隊頭)進行刪除操作(出隊),在表的后端(隊尾)進行插入操作(入隊)。隊列的特點是先進先出(FIFO-first in first out),即最先插入的元素最先被刪除。
為什么要引入隊列?
我們知道代碼的執行流有異步與同步之分,例如
var a = 1; setTimeout(function(){ a = 2; },0) alert(a) //1
我們一直習慣於“線性”地編寫代碼邏輯,但是在JavaScript編程幾乎總是伴隨着異步操作:
setTimeout,CSS3 Transition/Animation,ajax,dom的繪制,postmessage,Web Database等等,大量異步操作所帶來的回調函數,會把我們的算法分解地支離破碎
之前我們說過對於異步+回調的模式,怎么“拉平”異步操作,使之跟同步一樣,因為異步操作進行流程控制的時候無非避免的要嵌套大量的回調邏輯,所以就會出現promises約定了
那么jQuery引入隊列其實從一個角度上可以認為:允許一系列函數被異步地調用而不會阻塞程序
$("#Aaron").slideUp().fadeIn()
這是jQuery的一組動畫鏈式序列,它的內部其實就是一組隊列Queue,所以隊列和Deferred地位類似, 是一個內部使用的基礎設施,當slideUp運行時,fadeIn被放到fx隊列中,當slideUp完成后,從隊列中被取出運行。queue函數允許 直接操作這個鏈式調用的行為。同時,queue可以指定隊列名稱獲得其他能力,而不局限於fx隊列
jQuery提供了2組隊列操作的API:
- jQuery.queue/dequeue
- jQuery.fn.queue/dequeue
但是不同與普通隊列定義的是:jQuery.queue和jQuery.fn.queue不僅執行出隊操作,返回隊頭元素,還會自動執行返回的隊頭元素
fn是擴展在原型上的高級API是提供給實例使用的,.queue/.dequeue, 其內部是調用的$.queue,$.dequeue靜態的底層方法實現入列與出列
$.queue : 顯示或操作匹配的元素上已經執行的函數列隊
這個方法有兩個作用,它既是setter,又是getter。第一個參數elem是DOM元素,第二個參數type是字符串,第三個參數data可以是function或數組。
var body = $('body'); function cb1() {alert(1)} function cb2() {alert(2)} //set $.queue(body, 'aa', cb1); // 第三個參數為function $.queue(body, 'aa', cb2); //get $.queue(body, 'aa') //[function ,function]
這個方法有點類型get有點類似隊列的push操作,jQuery的方法的接口重載是非常嚴重的,經常同一個接口即是set也是get,不管符不符合基本原則,但是它卻很實用
無非就是把數據給緩存起來,為什么載體是一個jQuery對象,因為保存數據的手段是通過data數據緩存實現的
data_priv = new Data();
queue: function(elem, type, data) { var queue; if (elem) { type = (type || "fx") + "queue"; queue = data_priv.get(elem, type); // Speed up dequeue by getting out quickly if this is just a lookup if (data) { if (!queue || jQuery.isArray(data)) { queue = data_priv.access(elem, type, jQuery.makeArray(data)); } else { queue.push(data); } } return queue || []; } },
data與jQuery對象之間是通過uuid建立了一個無耦合的映射關系,具體可以翻閱之前的關於“數據緩存”
源碼有一個默認處理
type = (type || "fx") + "queue"
可見是專職供fx動畫隊列處理的
$.dequeue : 匹配的元素上執行隊列中的下一個函數
var body = $('body'); function cb1() {console.log(11111)} function cb2() {console.log(22222)} //set $.queue(body, 'aa', cb1); // 第三個參數為function $.queue(body, 'aa', cb2); $.dequeue(body, 'aa') //11 $.dequeue(body, 'aa') //22
出列就有點類似shift的操作,但是不同的是還會執行這個cb1與cb2
將回調函數出列執行,每調用一次僅出列一個,因此當回調有N個時,需要調用$.dequeue方法N次元素才全部出列
來看看源碼:
var queue = jQuery.queue(elem, type), startLength = queue.length, fn = queue.shift(), hooks = jQuery._queueHooks(elem, type), next = function() { jQuery.dequeue(elem, type); };
知道原理了, 這個就很簡單了,通過queue的get取出隊列的所有數據,判斷一下長度,然后截取出第一個,然后做好一個預處理生成下一個的next
這里有一個hooks?
仔細分析下這個內部queueHooks
_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() { data_priv.remove(elem, [type + "queue", key]); }) }); }
我們說了dequeue不僅是取出來還需要執行,在執行的時候把next與hooks傳遞給外部的回調,
這就是js的邏輯上的很繞的地方,在內部可以傳遞一個引用出去,又能提供外部調用或者執行
fn.call(elem, next, hooks)
因為傳遞了next,所以我們的代碼可以這樣改
var body = $('body'); function cb1(next,hoost) { console.log(11111) next() //執行了cb2 //22222 } function cb2() { console.log(22222) } //set $.queue(body, 'aa', cb1); // 第三個參數為function $.queue(body, 'aa', cb2); $.dequeue(body, 'aa')
next內部仍然調用$.dequeue,這樣可以接着執行隊列中的下一個callback
$.dequeue里的hooks是當隊列里所有的callback都執行完后(此時startLength為0)進行最后的一個清理工作
if ( !startLength && hooks ) { hooks.empty.fire(); }
鈎子其實就是jQuery.Callbacks對象,可以實現一個收集器的功能,至於在什么情況下時候,之后動畫中開始分析
所以隊列的本質是利用Array的push和shift來完成先進先出(First In First Out),但是這個方法的缺點也很明顯,無法單獨做一個獨立的模塊處理,因為它必須要跟jQuery對象吻合,而且對傳遞的數據只能是函數