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_user
和data_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就引入了數據對象這個概念
先不管內部怎么實現,先看節點的屬性
多了一個灰色的自定義的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)的時候,我們查看下緩存
發現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的處理,只是說在執行開始與執行完畢做了一個流程的控制
具體動畫內部怎么執行的,從下章開始分析