jQuery 2.0.3 源碼分析 數據緩存

歷史背景:node

jQuery從1.2.3版本引入數據緩存系統,主要的緣由就是早期的事件系統 Dean Edwards 的 ddEvent.js代碼 jquery

帶來的問題git

  • 沒有一個系統的緩存機制,它把事件的回調都放到EventTarget之上,這會引起循環引用
  • 若是EventTarget是window對象,又會引起全局污染
  • 不一樣模塊之間用不一樣緩存變量

 

通常jQuery開發,咱們都喜歡便捷式的把不少屬性,好比狀態標誌都寫到dom節點中,也就是HTMLElementgithub

好處:直觀,便捷數組

壞處:瀏覽器

  • 循環引用
  • 直接暴露數據,安全性?
  • 增長一堆的自定義屬性標籤,對瀏覽器來講是沒意義的
  • 取數據的時候要對HTML節點作操做

 


什麼是內存泄露緩存

內存泄露是指一塊被分配的內存既不能使用,又不能回收,直到瀏覽器進程結束。在C++中,由於是手動管理內存,內存泄露是常常出現的事情。而如今流行的C#和Java等語言採用了自動垃圾回收方法管理內存,正常使用的狀況下幾乎不會發生內存泄露。瀏覽器中也是採用自動垃圾回收方法管理內存,但因爲瀏覽器垃圾回收方法有bug,會產生內存泄露。安全

 

內存泄露的幾種狀況閉包

  • 循環引用
  • Javascript閉包
  • DOM插入順序

一個DOM對象被一個Javascript對象引用,與此同時又引用同一個或其它的Javascript對象,這個DOM對象可能會引起內存泄漏。這個DOM對象的引用將不會在腳本中止的時候被垃圾回收器回收。要想破壞循環引用,引用DOM元素的對象或DOM對象的引用須要被賦值爲null。dom

有DOM對象的循環引用將致使大部分當前主流瀏覽器內存泄露

第一種:多個對象循環引用

var a=new Object;

var b=new Object;

a.r=b;

b.r=a;

第二種:循環引用本身

var a=new Object;

a.r=a;

循環引用很常見且大部分狀況下是無害的,但當參與循環引用的對象中有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( element, key, value )

.data( )

對於jQuery.data方法,原文以下

The jQuery.data() method allows us to attach data of any type to DOM elements in a way that is safe from circular references and therefore from memory leaks. We can set several distinct values for a single element and retrieve them later:

在jQuery的官方文檔中,提示用戶這是一個低級的方法,應該用.data()方法來代替。$.data( element, key, value )能夠對DOM元素附加任何類型的數據,但應避免循環引用而致使的內存泄漏問題

都是用來在元素上存放數據也就平時所說的數據緩存,都返回jQuery對象,可是內部的處理確有本質的區別

咱們看一組對比

<div id="aaron">Aron test</div>
var aa1=$("#aaron");
var aa2=$("#aaron");

//=======第一組=========
$(''
).data()方法

aa1.data('a',1111);
aa2.data('a',2222);

aa1.data('a')  //結果222222
aa2.data('a')  //結果222222

//=======第二組=========
$.data()方法

$.data(aa1,"b","1111")
$.data(aa2,"b","2222")

$.data(aa1,"b")   //結果111111
$.data(aa2,"b")   //結果222222

意外嗎?,這樣的細節之前是否注意到呢?

怎麼經過.data()方法會覆蓋前面key相同的值呢?

 


對於jQuery來講,數據緩存系統原本就是爲事件系統服務而分化出來的,到後來,它的事件克隆乃至後來的動畫列隊實現數據的存儲都是離不開緩存系統,因此數據緩存也算是jQuery的一個核心基礎了

早期jQuery的緩存系統是把全部數據都放$.cache之上,而後爲每一個要使用緩存系統的元素節點,文檔對象與window對象分配一個UUID

data的實現不像attr直接把數據做爲屬性捆綁到元素節點上,若是爲DOM Element 附加數據;DOM Element 也是一種 Object ,但 IE六、IE7 對直接附加在 DOM Element 上的對象的垃圾回收存在問題;所以咱們將這些數據存放在全局緩存(咱們稱之爲「globalCache」)中,即 「globalCache」 包含了多個 DOM Element 的 「cache」,並在 DOM Element 上添加一個屬性,存放 「cache」 對應的 uid

 

$().data('a') 在表現形式上,雖然是關聯到dom上的,可是實際上處理就是在內存區開闢一個cache的緩存


那麼JQuery內部是如何處理,各類關聯狀況與操做呢?

 

******************$(‘’).data()的實現方式********************

用name和value爲對象附加數據

var obj = {};
    
    $.data(obj, 'name', 'aaron');

    $.data(obj,'name') //aaron

 

一個對象爲對象附加數據

var obj = {};

    $.data(obj, {
        name1: 'aaron1',
        name2: 'aaron1'
    });

    $.data(obj)   //Object {name1: "aaron1", name2: "aaron1"}

 

爲 DOM Element 附加數據

咱們用最簡單的代碼來闡述這個處理的流程:

1.獲取節點body

var $body = $("body")

2.給body上增長一條數據,屬性爲foo,值爲52

$body.data("foo", 52);

3.取出foo

$body.data('foo')

 

考慮一個問題:

一個元素在正常狀況下可使用.remove()方法將其刪除,並清除各自的數據。但對於本地對象而言,這是不能完全刪除的,這些相關的數據一直持續到窗口對象關閉

一樣,這些問題也存在於event 對象中,由於事件處理器(handlers)也是用該方法來存儲的。

那麼,要解決該問題最簡單的方法是將數據存儲到本地對象新增的一個屬性之中

因此如流程二解析同樣增長一個unlock標記

cache與elem 都統一塊兒來

if ( elem.nodeType ) {
        cache[ id ] = dataObject;     
        elem[ expando ] = id;
    } else {
        elem[ expando ] = dataObject;
    }

 


**************實現解析****************

(1)先在jQuery內部建立一個cache對象{}, 來保存緩存數據。 而後往須要進行緩存的DOM節點上擴展一個值爲expando的屬性,

function Data() {
    Object.defineProperty( this.cache = {}, 0, {
        get: function() {
            return {};
        }
    });
    this.expando = jQuery.expando + Math.random();
}

注:expando的值,用於把當前數據緩存的UUID值作一個節點的屬性給寫入到指定的元素上造成關聯橋樑,因此,因此元素自己具備這種屬性的可能性不多,因此能夠忽略衝突。

 

(2)接着把每一個節點的dom[expando]的值都設爲一個自增的變量id,保持全局惟一性。 這個id的值就做爲cache的key用來關聯DOM節點和數據。也就是說cache[id]就取到了這個節點上的全部緩存,即id就比如是打開一個房間(DOM節點)的鑰匙。 而每一個元素的全部緩存都被放到了一個map映射裏面,這樣能夠同時緩存多個數據。

Data.uid = 1;

關聯起dom對象與數據緩存對象的一個索引標記,換句話說

先在dom元素上找到expando對應值,也就uid,而後經過這個uid找到數據cache對象中的內容

 

(3)因此cache對象結構應該像下面這樣:

var cache = {
    "uid1": { // DOM節點1緩存數據,
        "name1": value1,
        "name2": value2
    },
    "uid2": { // DOM節點2緩存數據,
        "name1": value1,
        "name2": value2
    }
    // ......
};

每一個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中取到對應的數據

流程圖:

ppt8158.pptm [自動保存的]

整個過程結束,其實分解後邏輯很簡單的,只是要處理各類狀況下,代碼結構封裝就顯得很複雜了

 
如圖

imageimage

Body元素:expando:uid

jQuery203054840829130262140.37963378243148327: 3

 

數據緩存cache

uid:Object

 


那麼jQuery.data() 與 .data()  有什麼區別?

1.jQuery.data(element,[key],[value])源代碼

jQuery.extend({
        acceptData: Data.accepts,
        hasData: function( elem ){},
       //直接調用 data_user.access 數據類的接口,傳入的是elem整個jQuery對象
        data: function( elem, name, data ) {
            return data_user.access( elem, name, data );
        },
        ........


2.data([key],[value])

jQuery.fn.extend({
        data: function( elem, name, data ) {
            return jQuery.access( this, function( value )){
                //區別在each方法了,處理的是每個元素dom節點
                this.each(function() {
                    
                }  
            }
        }
        },
        ........


源代碼從源碼的簡單對比就很明顯的看出來

  • 看jQuery.data(element,[key],[value]),每個element都會有本身的一個{key:value}對象保存着數據,因此新建的對象就算有key相同它也不會覆蓋原來存在的對象key所對應的value,由於新對象保存是是在另外一個{key:value}對象中
  • $("div").data("a","aaaa") 它是把數據綁定每個匹配div節點的元素上

 

源碼能夠看出來,說到底,數據緩存就是在目標對象與緩存體間創建一對一的關係,整個Data類其實都是圍繞着 thia.cache 內部的數據作 增刪改查的操做

 

整個結構一目瞭然!

淺顯易懂的分析,讓你們都可以理解其思路,可是實現代碼上面確實很精妙,你們能夠圍繞這個思路去看,若是有須要我能夠在下篇把具體的源碼給寫一下

相關文章
相關標籤/搜索