溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

jqeury數(shù)據(jù)緩存之data()解析

發(fā)布時間:2020-08-02 22:41:51 來源:網絡 閱讀:2562 作者:chen2009277025 欄目:web開發(fā)

     以下文章的前半部分是我看的時候從網絡上面找的一篇指導性質的文章,寫的還不錯,相同的道理和話就不重復的說了,主要是語言表達能力也不好。所以我拿過來用用,后半段是我自己的解讀。

        考慮到作者的版權問題我們附上作者原文章地址:http://www.cnblogs.com/aaronjs/p/3370176.html

        一般jQuery開發(fā),我們都喜歡便捷式的把很多屬性,比如狀態(tài)標志都寫到dom節(jié)點中,也就是HTMLElement

好處:直觀,便捷

壞處:

  • 循環(huán)引用

  • 直接暴露數(shù)據(jù),安全性?

  • 增加一堆的自定義屬性標簽,對瀏覽器來說是沒意義的

  • 取數(shù)據(jù)的時候要對HTML節(jié)點做操作

什么是內存泄露

內存泄露是指一塊被分配的內存既不能使用,又不能回收,直到瀏覽器進程結束。在C++中,因為是手動管理內存,內存泄露是經常出現(xiàn)的事情。而現(xiàn)在流行的C#和Java等語言采用了自動垃圾回收方法管理內存,正常使用的情況下幾乎不會發(fā)生內存泄露。瀏覽器中也是采用自動垃圾回收方法管理內存,但由于瀏覽器垃圾回收方法有bug,會產生內存泄露。

內存泄露的幾種情況

  • 循環(huán)引用

  • Javascript閉包

  • DOM插入順序

一個DOM對象被一個Javascript對象引用,與此同時又引用同一個或其它的Javascript對象,這個DOM對象可能會引發(fā)內存泄漏。這個DOM對象的引用將不會在腳本停止的時候被垃圾回收器回收。要想破壞循環(huán)引用,引用DOM元素的對象或DOM對象的引用需要被賦值為null。

有DOM對象的循環(huán)引用將導致大部分當前主流瀏覽器內存泄露

第一種:多個對象循環(huán)引用

jqeury數(shù)據(jù)緩存之data()解析

var a=new Object;var b=new Object;

a.r=b;

b.r=a;

jqeury數(shù)據(jù)緩存之data()解析

第二種:循環(huán)引用自己

var a=new Object;

a.r=a;

循環(huán)引用很常見且大部分情況下是無害的,但當參與循環(huán)引用的對象中有DOM對象或者ActiveX對象時,循環(huán)引用將導致內存泄露。

我們把例子中的任何一個new Object替換成document.getElementById或者document.createElement就會發(fā)生內存泄露了。

 

具體的就深入討論了,這里的總結

  • JS的內存泄露,無怪乎就是從DOM中remove了元素,但是依然有變量或者對象引用了該DOM對象。然后內存中無法刪除。使得瀏覽器的內存占用居高不下。這種內存占用,隨著瀏覽器的刷新,會自動釋放。

  • 而另外一種情況,就是循環(huán)引用,一個DOM對象和JS對象之間互相引用,這樣造成的情況更嚴重一些,即使刷新,內存也不會減少。這就是嚴格意義上說的內存泄露了。

 

所以在平時實際應用中, 我們經常需要給元素緩存一些數(shù)據(jù),并且這些數(shù)據(jù)往往和DOM元素緊密相關。由于DOM元素(節(jié)點)也是對象, 所以我們可以直接擴展DOM元素的屬性,但是如果給DOM元素添加自定義的屬性和過多的數(shù)據(jù)可能會引起內存泄漏,所以應該要盡量避免這樣做。 因此更好的解決方法是使用一種低耦合的方式讓DOM和緩存數(shù)據(jù)能夠聯(lián)系起來。

jQuery引入緩存的作用

  • 允許我們在DOM元素上附加任意類型的數(shù)據(jù),避免了循環(huán)引用的內存泄漏風險

  • 用于存儲跟dom節(jié)點相關的數(shù)據(jù),包括事件,動畫等

  • 一種低耦合的方式讓DOM和緩存數(shù)據(jù)能夠聯(lián)系起來

jQuery緩存系統(tǒng)的真正魅力在于其內部應用中,動畫、事件等都有用到這個緩存系統(tǒng)。試想如果動畫的隊列都存儲到各DOM元素的自定義屬性中,這樣雖然可以方便的訪問隊列數(shù)據(jù),但也同時帶來了隱患。如果給DOM元素添加自定義的屬性和過多的數(shù)據(jù)可能會引起內存泄漏,所以要盡量避免這么干。

 

數(shù)據(jù)緩存接口

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元素附加任何類型的數(shù)據(jù),但應避免循環(huán)引用而導致的內存泄漏問題

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

我們看一組對比

<div id="aaron">Aron test</div>

jqeury數(shù)據(jù)緩存之data()解析

var aa1=$("#aaron");var aa2=$("#aaron");//=======第一組=========

$(''

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

aa1.data('a')  //結果222222aa2.data('a')  //結果222222//=======第二組=========$.data()方法$.data(aa1,"b","1111")
$.data(aa2,"b","2222")

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

jqeury數(shù)據(jù)緩存之data()解析

意外嗎?,這樣的細節(jié)以前是否注意到呢?

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

 


對于jQuery來說,數(shù)據(jù)緩存系統(tǒng)本來就是為事件系統(tǒng)服務而分化出來的,到后來,它的事件克隆乃至后來的動畫列隊實現(xiàn)數(shù)據(jù)的存儲都是離不開緩存系統(tǒng),所以數(shù)據(jù)緩存也算是jQuery的一個核心基礎了

早期jQuery的緩存系統(tǒng)是把所有數(shù)據(jù)都放$.cache之上,然后為每個要使用緩存系統(tǒng)的元素節(jié)點,文檔對象與window對象分配一個UUID

data的實現(xiàn)不像attr直接把數(shù)據(jù)作為屬性捆綁到元素節(jié)點上,如果為DOM Element 附加數(shù)據(jù);DOM Element 也是一種 Object ,但 IE6、IE7 對直接附加在 DOM Element 上的對象的垃圾回收存在問題;因此我們將這些數(shù)據(jù)存放在全局緩存(我們稱之為“globalCache”)中,即 “globalCache” 包含了多個 DOM Element 的 “cache”,并在 DOM Element 上添加一個屬性,存放 “cache” 對應的 uid

 

$().data('a') 在表現(xiàn)形式上,雖然是關聯(lián)到dom上的,但是實際上處理就是在內存區(qū)開辟一個cache的緩存


那么JQuery內部是如何處理,各種關聯(lián)情況與操作呢?

 

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

用name和value為對象附加數(shù)據(jù)

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

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

 

一個對象為對象附加數(shù)據(jù)

jqeury數(shù)據(jù)緩存之data()解析

var obj = {};

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

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

jqeury數(shù)據(jù)緩存之data()解析

 

為 DOM Element 附加數(shù)據(jù)

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

1.獲取節(jié)點body

var $body = $("body")

2.給body上增加一條數(shù)據(jù),屬性為foo,值為52

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

3.取出foo

$body.data('foo')

 

考慮一個問題:

一個元素在正常情況下可以使用.remove()方法將其刪除,并清除各自的數(shù)據(jù)。但對于本地對象而言,這是不能徹底刪除的,這些相關的數(shù)據(jù)一直持續(xù)到窗口對象關閉

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

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

所以如流程二解析一樣增加一個unlock標記

cache與elem 都統(tǒng)一起來

jqeury數(shù)據(jù)緩存之data()解析

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

jqeury數(shù)據(jù)緩存之data()解析

 


**************實現(xiàn)解析****************

(1)先在jQuery內部創(chuàng)建一個cache對象{}, 來保存緩存數(shù)據(jù)。 然后往需要進行緩存的DOM節(jié)點上擴展一個值為expando的屬性,

jqeury數(shù)據(jù)緩存之data()解析

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

jqeury數(shù)據(jù)緩存之data()解析

注:expando的值,用于把當前數(shù)據(jù)緩存的UUID值做一個節(jié)點的屬性給寫入到指定的元素上形成關聯(lián)橋梁,所以,所以元素本身具有這種屬性的可能性很少,所以可以忽略沖突。

 

(2)接著把每個節(jié)點的dom[expando]的值都設為一個自增的變量id,保持全局唯一性。 這個id的值就作為cache的key用來關聯(lián)DOM節(jié)點和數(shù)據(jù)。也就是說cache[id]就取到了這個節(jié)點上的所有緩存,即id就好比是打開一個房間(DOM節(jié)點)的鑰匙。 而每個元素的所有緩存都被放到了一個map映射里面,這樣可以同時緩存多個數(shù)據(jù)。

Data.uid = 1;

關聯(lián)起dom對象與數(shù)據(jù)緩存對象的一個索引標記,換句話說

先在dom元素上找到expando對應值,也就uid,然后通過這個uid找到數(shù)據(jù)cache對象中的內容

 

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

jqeury數(shù)據(jù)緩存之data()解析

var cache = {    "uid1": { // DOM節(jié)點1緩存數(shù)據(jù),
        "name1": value1,        "name2": value2
    },    "uid2": { // DOM節(jié)點2緩存數(shù)據(jù),
        "name1": value1,        "name2": value2
    }    // ......};

jqeury數(shù)據(jù)緩存之data()解析

每個uid對應一個elem緩存數(shù)據(jù),每個緩存對象是可以由多個name/value(名值對)對組成的,而value是可以是任何數(shù)據(jù)類型的。

 

流程分解:(復雜的過濾,找重的過程去掉)

第一步:jQuery本身就是包裝后的數(shù)組結構,這個不需要解析了

第二步:通過data存儲數(shù)據(jù)

  • 為了把不把數(shù)據(jù)與dom直接關聯(lián),所以會把數(shù)據(jù)存儲到一個cache對象上

  • 產生一個 unlock = Data.uid++; unlock 標記號

  • 把unlock標記號,作為一個屬性值 賦予$body節(jié)點

  • cache緩存對象中開辟一個新的空間用于存儲foo數(shù)據(jù),this.cache[ unlock ] = {};

  • 最后把foo數(shù)據(jù)掛到cache上,cache[ data ] = value;

第三步:通過data獲取數(shù)據(jù)

  • 從$body節(jié)點中獲取到unlock標記

  • 通過unlock在cache中取到對應的數(shù)據(jù)

流程圖:

jqeury數(shù)據(jù)緩存之data()解析

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

 
Body元素:expando:uid

jQuery203054840829130262140.37963378243148327: 3

 

數(shù)據(jù)緩存cache

uid:Object

 


那么jQuery.data() 與 .data()  有什么區(qū)別?

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

jqeury數(shù)據(jù)緩存之data()解析

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

jqeury數(shù)據(jù)緩存之data()解析


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

jqeury數(shù)據(jù)緩存之data()解析

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

jqeury數(shù)據(jù)緩存之data()解析


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

  • 看jQuery.data(element,[key],[value]),每一個element都會有自己的一個{key:value}對象保存著數(shù)據(jù),所以新建的對象就算有key相同它也不會覆蓋原來存在的對象key所對應的value,因為新對象保存是是在另一個{key:value}對象中

  • $("div").data("a","aaaa") 它是把數(shù)據(jù)綁定每一個匹配div節(jié)點的元素上

 

源碼可以看出來,說到底,數(shù)據(jù)緩存就是在目標對象與緩存體間建立一對一的關系,整個Data類其實都是圍繞著 thia.cache 內部的數(shù)據(jù)做 增刪改查的操作

      為了更準確的理解jq的data函數(shù)處理,我自己寫了一個方法來實現(xiàn)data,數(shù)據(jù)緩存:

var $ = {};
    //這個模擬的就是jquery中的全局的緩存
    $.catche = {};
    //這個就是模擬了jq的全局緩存的一個標志key
    $.internalKey = (new Date()).getTime();
    //這個就是類uuid的用法
    $.prop_index = 1;

    //獲取得到元素
    var obj = document.querySelector("#testDiv");

    /***
    *這就是模擬jq的data方法做的數(shù)據(jù)緩存
    **/
    function data(obj,attr,val){
        //判斷obj是否是節(jié)點
        var isNode = obj.nodeType;
        var ele;
        //參數(shù)修正
        if(!isNode){
            if(arguments.length <= 2){
                val =  attr;
                attr = obj;
                ele = this;
            }
        }else{
           ele = obj;
        }

        isNode = ele.nodeType;

        //獲得數(shù)據(jù)的id
        var id = isNode?ele[$.internalKey]:ele[$.internalKey]&&$.internalKey;

        //如果不存在id
        if(!id){
            //在jq里面還實現(xiàn)了,從已經刪除的id序列里面啟用刪除過的id
            id = isNode?ele[$.internalKey]= $.prop_index++: $.internalKey;
        }

        var valObj = isNode?$.catche:ele;

        if(!valObj[id]){
            valObj[id] = {};
        }

        var returnData = "";

        if(typeof attr == "string"){
            if(val != undefined)
            {
                returnData = val;
                valObj[id][attr] = val;
            }else{
                returnData = valObj[id][attr];
            }
        }else{
            returnData = valObj[id];
        }
        $.catche = valObj;
        return returnData;
    }

    obj.data = data;

//    console.log(data(obj,"hell","1212"));
//    console.log(data(obj,"hell"));

    obj.data("hello","aaa");

    console.log("val:"+obj.data("hello"));

       通過自己的測試實現(xiàn)了jq的data的數(shù)據(jù)緩存,也無需在dom元素節(jié)點上面添加任何屬性,這個大概就是jquery的實現(xiàn)data數(shù)據(jù)緩存的機制。如果有理解不當?shù)牡胤酵刚?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI