1. 程式人生 > >jQuery原始碼分析系列(39) : 動畫佇列

jQuery原始碼分析系列(39) : 動畫佇列

data函式在jQuery中只有短短的300行程式碼,非常不起點 ,剖析原始碼的時候你會發現jQuery只要在有需要儲存資料的地方無時無刻不依賴這個基礎設施

動畫會呼叫佇列,佇列會呼叫data資料介面還儲存佇列裡面的的動畫資料

所以我們在自習回顧下關於資料快取

//These may be used throughout the jQuery core codebase
//存資料的
//使用者使用
data_user = new Data();
//儲存物件
//jQuery內部私有
//用來存事件的, 如click事件那種
data_priv = new Data();

data在jQuery中有兩種

一個是用來存資料的, 對應的物件分別是

儲存物件: data_user

獲取與設定方法: $.data(el, key, value)

另一個是用來存事件的,動畫資料的

儲存物件: data_priv

獲取與設定方法: $._data(el, key, value)

data_userdata_priv, 就如其名, 一個是使用者用的, 一個是jQuery私有的, 他們都是一個叫Data的例項物件

為什麼要設定這2個數據介面類?

jQuery的設定都是維護一個jQuery的資料物件,所以

只是實現data的介面,比如:

$('#aaron').data('key', 'value')

實現鏈式的.data介面,這種很簡單,我們可以把簡單的資料快取到jquery內部的這個物件上

方法可以這樣實現

直接在例項上fn的介面上擴充

$.fn.data = function(k, v) {
    return this.each(function() {
        this[key] = v //把介面data的value儲存在dom物件上
    })
    return this
}

這個很簡單把資料直接儲存在dom物件上

當然針對基本型別這樣處理當然也是可以的,如果是引用型別,函式物件呢?這樣處理可以嗎?

$('#aaron').data('nodeValue', '11111')

$(
'#aaron').data('key', function(){ //操作 })

問題來了:

1:這樣會更改DOM本身的屬性值,當然能不能生效還不說

2:傳遞可是引用型別哦,不靠譜的記憶體回收,說不定就溢位了

3:資料暴露,容易被直接改寫

如何解決這些問題? jQuery就引入了資料物件這個概念

先不管內部怎麼實現,先看節點的屬性

image

多了一個灰色的自定義的key與value

灰色意味著不能通過for枚舉出來,這種設定在ES5中,是有API直接支援了

// If not, create one
if (!unlock) {
    unlock = Data.uid++;
    // Secure it in a non-enumerable, non-writable property
    try {
        descriptor[this.expando] = {
            value: unlock
        };
        Object.defineProperties(owner, descriptor);
        // Support: Android < 4
        // Fallback to a less secure definition
    } catch (e) {
        descriptor[this.expando] = unlock;
        jQuery.extend(owner, descriptor);
    }
}

看註釋就清晰了,這個屬性是受保護的,不能被改寫

OK了。通過這樣一個唯一的橋接標誌,我們可以做一個ORM的映射了,讓dom與一個數據快取介面產生一一對應的關係

動畫佇列用到的資料快取

jQuery為了實現動畫佇列的鏈式呼叫,所以必須先在例項就是原型上先擴充套件一個方法啊,然後在內部才能呼叫底層的方法

當然jQuery基本所有的層次都是這樣的結構,除了鏈式之外,還可以把具體的例項方法與原型方法通用

//靜態
jQuery.extend
//例項
.extend

動畫的例項方法

this.queue(optall.queue, doAnimation);

呼叫例項方法中基於動畫的擴充套件介面

jQuery.fn.extend({
    queue
    dequeue
    delay
    clearQueue
    promise
queue: function(type, data) {
    var setter = 2;
    //修正type, 預設為表示jquery動畫的fx, 如果不為"fx", 
    //即為自己的自定義動畫, 一般我們用"fx"就足夠了.  
    if (typeof type !== "string") {
        data = type;
        type = "fx";
        setter--;
    }

    //只有動畫的回撥
    // div.slideToggle(1000);
    // div.slideToggle("fast");
    // div.animate({left:'-=200'},1500);
    // div.queue('fx')
    if (arguments.length < setter) {
        return jQuery.queue(this[0], type);
    }

    return data === undefined ?
        this :
        this.each(function() {
            //呼叫基礎佇列
            //設定動畫佇列快取
            //並返回佇列總數
            var queue = jQuery.queue(this, type, data);

            // ensure a hooks for this queue
            jQuery._queueHooks(this, type);

            //直接執行動畫佇列
            //防止在執行函式的時候, 這裡又進行dequeue操作, 這樣會同時執行2個函式, 佇列就不受控制了.  
            if (type === "fx" && queue[0] !== "inprogress") {
                //如果佇列沒有被鎖住, 即此時沒有在執行dequeue. 移出佇列裡第一個函式並執行它.  
                jQuery.dequeue(this, type);
            }
        });
},

例項的queue介面只是做了2個情況的判斷,一種是傳遞fx,一種是設定佇列,底層還是通過jQuery.queue靜態方法處理的

分析一組動畫:

div.show(1000);
div.hide(2000)
div.show(3000)

1 有時間引數的介面,是肯定需要執行動畫的,同一個介面上有多個動畫介面,那麼就會意味著需要佇列來管理執行順序

2 管理佇列引入資料快取,快取需要載體node節點,所以動畫的擴充套件的介面在最上層設計是可以直接跟DOM鏈式

邏輯上肯定是線性的執行,show執行完畢,然後取出hide執行,完畢後在取出show執行

那麼動畫的呼叫是如何組織的?

理論上3個動畫,在cache中有3個快取的data我們檢視下

為了便於檢視,我把程式碼改了下 增加了一個標示 Aaron 對應的是時間

在執行div.show(3000)的時候,我們檢視下快取

image

發現0位置的div.show(1000);被inprogress給替代了

可以猜測下第一個動畫已經在開始執行了,那麼它在佇列中會用一個佔位符用來通知後面,我這個動畫還在進行,後面的動畫先等等

inprogress”程序鎖是這樣工作的:

如果是dequeue操作, 去掉鎖, 執行佇列裡的函式, 同時給佇列加上鎖. 如果是queue操作, 要看鎖的狀態, 如果被鎖上了, 就只執行佇列的新增操作. 不再呼叫dequeue.其實dequeue和queue都可以執行佇列裡的第一個函式.queue操作新增完佇列之後, 會呼叫dequeue方法去執行函式.

用dequeue執行函式的時候, 這時候如果又用queue觸發dequeue的話, 很可能同時有2個函式在執行. 佇列就失去一大半意義了(還是可以保證順序, 但是2個動畫會同時執行).不過這個鎖只能保證在dequeue的時候, 不被queue操作意外的破壞佇列.

如果人為的同時用2個dequeue, 還是會破壞動畫效果的. 所以要把fn寫在回撥函式裡

我們在第一次做push的時候就會開始執行了動畫,這樣可以讓速度更優

.queue(1000) , .queue(2000) , queue(3000)

if (type === "fx" && queue[0] !== "inprogress") {
    //如果佇列沒有被鎖住, 即此時沒有在執行dequeue. 移出佇列裡第一個函式並執行它.  
    jQuery.dequeue(this, type);
}

當第一個動畫執行完畢後,那麼必須有一個回撥通知這個去把佇列中下一個執行給取出來,然後要刪掉這個佔位,依次迴圈

opt.complete = function() {
    if (jQuery.isFunction(opt.old)) {
        opt.old.call(this);
    }

    if (opt.queue) {
        jQuery.dequeue(this, opt.queue);
    }
};

所以可見,動畫的執行其實最終是依賴queue與dequeue的處理,只是說在執行開始與執行完畢做了一個流程的控制

具體動畫內部怎麼執行的,從下章開始分析