jQuery原始碼分析系列(38) : 佇列操作
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,.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物件吻合,而且對傳遞的資料只能是函式
如果您看完本篇文章感覺不錯,請點選一下右下角的【推薦】來支援一下博主,謝謝!
如果是原創文章,轉載請註明出處!!!