曆史背景:
帶來的問題:
沒有一個系統的緩存機制,它把事件的回調都放到EventTarget之上,這會引發循環引用
如果EventTarget是window對象,又會引發全局污染
不同子產品之間用不同緩存變量
一般jQuery開發,我們都喜歡便捷式的把很多屬性,比如狀态标志都寫到dom節點中,也就是HTMLElement
好處:直覺,便捷
壞處:
循環引用
直接暴露資料,安全性?
增加一堆的自定義屬性标簽,對浏覽器來說是沒意義的
取資料的時候要對HTML節點做操作
什麼是記憶體洩露
記憶體洩露是指一塊被配置設定的記憶體既不能使用,又不能回收,直到浏覽器程序結束。在C++中,因為是手動管理記憶體,記憶體洩露是經常出現的事情。而現在流行的C#和Java等語言采用了自動垃圾回收方法管理記憶體,正常使用的情況下幾乎不會發生記憶體洩露。浏覽器中也是采用自動垃圾回收方法管理記憶體,但由于浏覽器垃圾回收方法有bug,會産生記憶體洩露。
記憶體洩露的幾種情況
Javascript閉包
DOM插入順序
一個DOM對象被一個Javascript對象引用,與此同時又引用同一個或其它的Javascript對象,這個DOM對象可能會引發記憶體洩漏。這個DOM對象的引用将不會在腳本停止的時候被垃圾回收器回收。要想破壞循環引用,引用DOM元素的對象或DOM對象的引用需要被指派為null。
含有DOM對象的循環引用将導緻大部分目前主流浏覽器記憶體洩露
第一種:多個對象循環引用
第二種:循環引用自己
循環引用很常見且大部分情況下是無害的,但當參與循環引用的對象中有DOM對象或者ActiveX對象時,循環引用将導緻記憶體洩露。
我們把例子中的任何一個new Object替換成document.getElementById或者document.createElement就會發生記憶體洩露了。
具體的就深入讨論了,這裡的總結
JS的記憶體洩露,無怪乎就是從DOM中remove了元素,但是依然有變量或者對象引用了該DOM對象。然後記憶體中無法删除。使得浏覽器的記憶體占用居高不下。這種記憶體占用,随着浏覽器的重新整理,會自動釋放。
而另外一種情況,就是循環引用,一個DOM對象和JS對象之間互相引用,這樣造成的情況更嚴重一些,即使重新整理,記憶體也不會減少。這就是嚴格意義上說的記憶體洩露了。
是以在平時實際應用中, 我們經常需要給元素緩存一些資料,并且這些資料往往和DOM元素緊密相關。由于DOM元素(節點)也是對象, 是以我們可以直接擴充DOM元素的屬性,但是如果給DOM元素添加自定義的屬性和過多的資料可能會引起記憶體洩漏,是以應該要盡量避免這樣做。 是以更好的解決方法是使用一種低耦合的方式讓DOM和緩存資料能夠聯系起來。
是以我們必須有一種機制,抽象出這樣的處理方式
jQuery引入緩存的作用
允許我們在DOM元素上附加任意類型的資料,避免了循環引用的記憶體洩漏風險
用于存儲跟dom節點相關的資料,包括事件,動畫等
一種低耦合的方式讓DOM和緩存資料能夠聯系起來
jQuery緩存系統的真正魅力在于其内部應用中,動畫、事件等都有用到這個緩存系統。試想如果動畫的隊列都存儲到各DOM元素的自定義屬性中,這樣雖然可以友善的通路隊列資料,但也同時帶來了隐患。如果給DOM元素添加自定義的屬性和過多的資料可能會引起記憶體洩漏,是以要盡量避免這麼幹。
資料緩存接口
對于jQuery.data方法,原文如下
在jQuery的官方文檔中,提示使用者這是一個低級的方法,應該用.data()方法來代替。$.data( element, key, value )可以對DOM元素附加任何類型的資料,但應避免循環引用而導緻的記憶體洩漏問題
都是用來在元素上存放資料也就平時所說的資料緩存,都傳回jQuery對象,但是内部的處理确有本質的差別
我們看一組對比
$(''
意外嗎?,這樣的細節以前是否注意到呢?
怎麼通過.data()方法會覆寫前面key相同的值呢?
對于jQuery來說,資料緩存系統本來就是為事件系統服務而分化出來的,到後來,它的事件克隆乃至後來的動畫列隊實作資料的存儲都是離不開緩存系統,是以資料緩存也算是jQuery的一個核心基礎了
早期jQuery的緩存系統是把所有資料都放$.cache之上,然後為每個要使用緩存系統的元素節點,文檔對象與window對象配置設定一個UUID
data的實作不像attr直接把資料作為屬性捆綁到元素節點上,如果為DOM Element 附加資料;DOM Element 也是一種 Object ,但 IE6、IE7 對直接附加在 DOM Element 上的對象的垃圾回收存在問題;是以我們将這些資料存放在全局緩存(我們稱之為“globalCache”)中,即 “globalCache” 包含了多個 DOM Element 的 “cache”,并在 DOM Element 上添加一個屬性,存放 “cache” 對應的 uid
$().data('a') 在表現形式上,雖然是關聯到dom上的,但是實際上處理就是在記憶體區開辟一個cache的緩存
那麼JQuery内部是如何處理,各種關聯情況與操作呢?
******************$(‘’).data()的實作方式********************
用name和value為對象附加資料
一個對象為對象附加資料
為 DOM Element 附加資料
我們用最簡單的代碼來闡述這個處理的流程:
1.擷取節點body
2.給body上增加一條資料,屬性為foo,值為52
3.取出foo
考慮一個問題:
一個元素在正常情況下可以使用.remove()方法将其删除,并清除各自的資料。但對于本地對象而言,這是不能徹底删除的,這些相關的資料一直持續到視窗對象關閉
同樣,這些問題也存在于event 對象中,因為事件處理器(handlers)也是用該方法來存儲的。
那麼,要解決該問題最簡單的方法是将資料存儲到本地對象新增的一個屬性之中
是以如流程二解析一樣增加一個unlock标記
cache與elem 都統一起來
**************實作解析****************
(1)先在jQuery内部建立一個cache對象{}, 來儲存緩存資料。 然後往需要進行緩存的DOM節點上擴充一個值為expando的屬性,
注:expando的值,用于把目前資料緩存的UUID值做一個節點的屬性給寫入到指定的元素上形成關聯橋梁,是以,是以元素本身具有這種屬性的可能性很少,是以可以忽略沖突。
(2)接着把每個節點的dom[expando]的值都設為一個自增的變量id,保持全局唯一性。 這個id的值就作為cache的key用來關聯DOM節點和資料。也就是說cache[id]就取到了這個節點上的所有緩存,即id就好比是打開一個房間(DOM節點)的鑰匙。 而每個元素的所有緩存都被放到了一個map映射裡面,這樣可以同時緩存多個資料。
關聯起dom對象與資料緩存對象的一個索引标記,換句話說
先在dom元素上找到expando對應值,也就uid,然後通過這個uid找到資料cache對象中的内容
(3)是以cache對象結構應該像下面這樣:
每個uid對應一個elem緩存資料,每個緩存對象是可以由多個name/value(名值對)對組成的,而value是可以是任何資料類型的。
流程分解:(複雜的過濾,找重的過程去掉)
第一步:jQuery本身就是包裝後的數組結構,這個不需要解析了
第二步:通過data存儲資料
為了把不把資料與dom直接關聯,是以會把資料存儲到一個cache對象上
産生一個 unlock = Data.uid++; unlock 标記号
把unlock标記号,作為一個屬性值 賦予$body節點
cache緩存對象中開辟一個新的空間用于存儲foo資料,this.cache[ unlock ] = {};
最後把foo資料挂到cache上,cache[ data ] = value;
第三步:通過data擷取資料
從$body節點中擷取到unlock标記
通過unlock在cache中取到對應的資料
流程圖:
整個過程結束,其實分解後邏輯很簡單的,隻是要處理各種情況下,代碼結構封裝就顯得很複雜了
如圖
Body元素:expando:uid
jQuery203054840829130262140.37963378243148327: 3
資料緩存cache
uid:Object
那麼jQuery.data() 與 .data() 有什麼差別?
1.jQuery.data(element,[key],[value])源代碼
2.data([key],[value])
源代碼從源碼的簡單對比就很明顯的看出來
看jQuery.data(element,[key],[value]),每一個element都會有自己的一個{key:value}對象儲存着資料,是以建立的對象就算有key相同它也不會覆寫原來存在的對象key所對應的value,因為新對象儲存是是在另一個{key:value}對象中
$("div").data("a","aaaa") 它是把資料綁定每一個比對div節點的元素上
源碼可以看出來,說到底,資料緩存就是在目标對象與緩存體間建立一對一的關系,整個Data類其實都是圍繞着 thia.cache 内部的資料做 增删改查的操作
本文轉自艾倫 Aaron部落格園部落格,原文連結:http://www.cnblogs.com/aaronjs/p/3370176.html,如需轉載請自行聯系原作者