【菜鳥學習jquery源碼】數據緩存與data()

前言

最近比較煩,深圳的工做還沒着落,論文不想弄,煩。。。。。今天看了下jquery的數據緩存的代碼,參考着Aaron的源碼分析,本身有點理解了,和你們分享下。之後也打算把本身的jquery的學習心得寫一個系列,固然和大神的源碼分析是比不了的,只是本身在看的時候有好多地方是比較難理解的,爲新手提供些便捷的學習方法,之後我會把我這些流水帳整理成一個菜鳥學習jquery源碼系列,如今就看到哪寫到那,見諒。node

內存泄露

首先看看什麼是內存泄露,這裏直接拿來Aaron中的這部分來講明什麼是內存泄露,內存泄露的3種狀況:jquery

  1 循環引用json

  2 Javascript閉包api

  3 DOM插入順序瀏覽器

在這裏咱們只解釋第一種狀況,由於jquery的數據緩存就是解決這類的內存泄露的。一個DOM對象被一個Javascript對象引用,與此同時又引用同一個或其它的Javascript對象,這個DOM對象可能會引起內存泄漏。這個DOM對象的引用將不會在腳本中止的時候被垃圾回收器回收。要想破壞循環引用,引用DOM元素的對象或DOM對象的引用須要被賦值爲null。緩存

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

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

複製代碼
var a=new Object;

var b=new Object;

a.r=b;

b.r=a;
複製代碼

第二種:循環引用本身less

var a=new Object;

a.r=a;

循環引用很常見且大部分狀況下是無害的,但當參與循環引用的對象中有DOM對象或者ActiveX對象時,循環引用將致使內存泄露。dom

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

在實際應用中咱們要給咱們的DOM添加數據,若是咱們給一個DOM添加的數據太多的話,會存在循環引用的風險,例如咱們添加的數據剛好引用了這個DOM元素,就會存在內存的泄露。因此jquery使用了數據緩存的機制就解決或者說避免這一問題。

數據緩存

$.cache 是jquery的緩存對象,這個是對象就是一個json,它的結構是這樣的

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

數據緩存的接口是

$.data( element, key, value )

$(selector).data(key,value)

用法

看代碼以前,先看看怎麼使用jquery的數據緩存。在jquery中,有兩個方法能夠給對象設置數據,分別是實例方法$().data()和靜態方法$.data(),具體的使用過程你們看api就知道了,這裏簡單介紹下

靜態方法$.data()有三個參數,分別是掛在數據的元素,掛載的數據鍵,掛載數據的值,根據參數的不一樣,無非就是設置數據,取數據,具體以下

 1 $.data( elem, key, value ) 在指定元素上存儲/添加任意的數據,處理了循環引用和內存泄漏問題
 2 $.data( elem, key ) 返回指定元素上name指定的值
 3 $.data( elem ) 返回所有數據
 4 $.data( elem,obj ) 在指定的元素上綁定obj 

var obj = {};
$.data(obj , "a" , 1);//普通對象添加數據
console.log($.data(obj,"a"));//1
var dom = $("body");//dom添加數據
$.data(dom,"a",1)
console.log($.data(dom,"a"));//1
$.data(obj , {"b":2});//兩個參數 綁定數據對象
console.log($.data(dom,"b"));//2
console.log($.data(dom));//1 2

靜態方法$().data()有兩個參數,掛載的數據鍵,掛載數據的值

 1 $(selector).data( key, value ) 在指定元素上存儲/添加任意的數據,處理了循環引用和內存泄漏問題
 2 $(selector).data( key ) 返回指定元素上name指定的值
 3 $(selector).data(obj ) 在指定的元素上綁定obj 
 4 $(selector).data() 返回所有數據

$("body").data("a" , 1);//添加數據
console.log($("body").data("a"));//1
$("body").data({"b":2});//兩個參數 綁定數據對象
console.log($("body").data("b"));//2
console.log($("body").data();//1 2

思路

回想下咱們要解決什麼問題:咱們想在DOM上添加數據,可是不想引發內存的泄露,也就是咱們不想引發循環引用,要儘可能減小在DOM上掛數據。jquery的思路是這樣:使用一個數據緩存對象$.cache,在須要綁定數據的DOM上擴展一個expando屬性,這個屬性存的是一個id,這裏不會存在循環引用的狀況了,以後將數據存在$.cache[id]上,當咱們取DOM上的數據的時候,咱們能夠根據DOM上的expando找到id,進而找到存在$.cache[id]上的數據。能夠看出jquery只是在DOM上擴展了一個屬性expando,數據都存在了$.cache中,利用expando這個屬性創建DOM和緩存對象之間的聯繫。不管咱們添加多少的數據都會存儲在緩存對象中,而不是直接掛在DOM上。這個惟一id是一個整型值,初始爲0,調用data接口時自動加一,惟一id附加在以$.expando命名的屬性上,$.expando是動態生成的,相似於一個時間戳,以儘量的避免與用戶變量衝突。從匹配的DOM元素上取到惟一id,在$.cache中找到惟一id對應的對象,再從對應的對象中找到key對應的值

看例子,在源碼裏打斷點看一下

$.data($("body")[0],{"a":1});
console.log($.data($("body")[0],"a"));

   

DOM對象擴展了一個屬性,這個屬性存的是cache的id。

                             

這樣你們就比較明顯了。

實現

expando就是一個相似時間戳的東東,源碼

expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" )

就是爲了生成標識的,沒啥可說的。

這是靜態方法的代碼的總體結構,我看到的1.10.2,變化較大,全部的方法的實現都封裝成了函數,主要看 internalData( elem, name, data )這個函數,其餘的大夥本身看看吧

jQuery.extend({
    cache: {},

    // The following elements throw uncatchable exceptions if you
    // attempt to add expando properties to them.
    noData: {
        "applet": true,
        "embed": true,
        // Ban all objects except for Flash (which handle expandos)
        "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
    },

    hasData: function( elem ) {
        elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
        return !!elem && !isEmptyDataObject( elem );
    },

    data: function( elem, name, data ) {
        return internalData( elem, name, data );
    },

    removeData: function( elem, name ) {
        return internalRemoveData( elem, name );
    },

    // For internal use only.
    _data: function( elem, name, data ) {
        return internalData( elem, name, data, true );
    },

    _removeData: function( elem, name ) {
        return internalRemoveData( elem, name, true );
    },

    // A method for determining if a DOM node can handle the data expando
    acceptData: function( elem ) {
        // Do not set data on non-element because it will not be cleared (#8335).
        if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
            return false;
        }

        var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];

        // nodes accept data unless otherwise specified; rejection can be conditional
        return !noData || noData !== true && elem.getAttribute("classid") === noData;
    }
});
View Code
function internalData( elem, name, data, pvt /* Internal Use Only */ ){
    if ( !jQuery.acceptData( elem ) ) {//查看是否能夠接受數據
        return;
    }
    var ret, thisCache,
        internalKey = jQuery.expando,//jQuery副本的惟一標識
        // We have to handle DOM nodes and JS objects differently because IE6-7
        // can't GC object references properly across the DOM-JS boundary
        isNode = elem.nodeType,//判斷DOM節點
        // Only DOM nodes need the global jQuery cache; JS object data is
        // attached directly to the object so GC can occur automatically
        cache = isNode ? jQuery.cache : elem,//如果是DOM對象,則cache就是$.cache,不然爲參數elem對象
        // Only defining an ID for JS objects if its cache already exists allows
        // the code to shortcut on the same path as a DOM node with no cache
        id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;//找id,id可能在DOM[expando]中,也能夠在elem[expando]中
    // Avoid doing any more work than we need to when trying to get data on an
    // object that has no data at all
    if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
        return;//參數的一些判斷限制
    }
    if ( !id ) {//id不存在
        // Only DOM nodes need a new unique ID for each element since their data
        // ends up in the global cache
        if ( isNode ) {//是DOM節點
            id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;//生成一個id
        } else {//不是DOM,是一個對象
            id = internalKey;//那麼id就是那個expando
        }
    }
    if ( !cache[ id ] ) {//cache中不存在數據,先弄成空的,一會在填充
        // Avoid exposing jQuery metadata on plain JS objects when the object
        // is serialized using JSON.stringify
        cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
    }
    // An object can be passed to jQuery.data instead of a key/value pair; this gets
    // shallow copied over onto the existing cache
    if ( typeof name === "object" || typeof name === "function" ) {//處理第二個參數時對象或者是函數的狀況
        if ( pvt ) {//不太懂
            cache[ id ] = jQuery.extend( cache[ id ], name );
        } else {//添加到data屬性上
            cache[ id ].data = jQuery.extend( cache[ id ].data, name );
        }
    }
    thisCache = cache[ id ];
    // jQuery data() is stored in a separate object inside the object's internal data
    // cache in order to avoid key collisions between internal data and user-defined
    // data.
    if ( !pvt ) {
        if ( !thisCache.data ) {
            thisCache.data = {};
        }
        thisCache = thisCache.data;
    }
    if ( data !== undefined ) {//第三個參數存在,就是存數據
        thisCache[ jQuery.camelCase( name ) ] = data;
    }
    // Check for both converted-to-camel and non-converted data property names
    // If a data property was specified
    if ( typeof name === "string" ) {

        // First Try to find as-is property data
        ret = thisCache[ name ];//取出來待返回的那個value
                //有啥用 這麼麻煩
        // Test for null|undefined property data
        if ( ret == null ) {
            // Try to find the camelCased property
            ret = thisCache[ jQuery.camelCase( name ) ];
        }
    } else {
        ret = thisCache;//就是返回存進來的那個對象或者函數
    }
    return ret; 
}

實現起來仍是比較簡單的,只是有些地方jquery考慮的太周全了,我等凡人看不太透徹。

pS:給DOM對象添加的數據是存儲在了$.cache中,而給對象添加書數據直接掛在了對象的expando上面。其實給一個對象掛數據也沒有什麼實際的意義。

看源碼能夠知道,看個例子更明顯

var obj = {};
$.data(obj,{"a":1});
console.log($.data(obj,"a"));
console.log(obj);

結果:

 實例方法data()其實就是調用了$.data()這個靜態方法,這裏就不說了。

jQuery.fn.extend({
    data: function( key, value ) {
        var attrs, name,
            data = null,
            i = 0,
            elem = this[0];

        // Special expections of .data basically thwart jQuery.access,
        // so implement the relevant behavior ourselves

        // Gets all values
        if ( key === undefined ) {
            if ( this.length ) {
                data = jQuery.data( elem );

                if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
                    attrs = elem.attributes;
                    for ( ; i < attrs.length; i++ ) {
                        name = attrs[i].name;

                        if ( name.indexOf("data-") === 0 ) {
                            name = jQuery.camelCase( name.slice(5) );

                            dataAttr( elem, name, data[ name ] );
                        }
                    }
                    jQuery._data( elem, "parsedAttrs", true );
                }
            }

            return data;
        }

        // Sets multiple values
        if ( typeof key === "object" ) {
            return this.each(function() {
                jQuery.data( this, key );
            });
        }

        return arguments.length > 1 ?

            // Sets one value
            this.each(function() {
                jQuery.data( this, key, value );//這是重點
            }) :

            // Gets one value
            // Try to fetch any internally stored data first
            elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
    },
View Code

問題

如今咱們利用源碼分析一些問題

        var a = $("body");
        var b = $("body");
        a.data("a",1);
        b.data("a",2);
        console.log(a.data("a"));//2
        console.log(b.data("a"));//2

        $.data(a,"b",1);
        $.data(b,"b",2);
        console.log($.data(a,"b"))//1
        console.log($.data(b,"b"))//2

        $.data(a[0],"b",1);
        $.data(b[0],"b",2);
        console.log($.data(a[0],"b"));//2
        console.log($.data(b[0],"b"));//2

看着有些暈,先看下這個

var a = $("body");
var b = $("body");
console.log(a[0] == b[0]);//true
console.log(a == b);//false
console.log( $("body") == $("body"));//false

每一次$("body")都生成一個新的對象,因此每一次都會不一樣,$("body")[0]都是指向同一個body對象,a 和b指向的每一個新對象的地址,因此不一樣。

看第一組

        var a = $("body");
        var b = $("body");
        a.data("a",1);
        b.data("a",2);
        console.log(a.data("a"));//2
        console.log(b.data("a"));//2

在看源代碼這句

this.each(function() {
                jQuery.data( this, key, value );
            }) 

調用$.data(),可是這裏第一個參數爲this,是原生的DOM對象,第一組中的a和b的DOM對象都是body,因此添加數據會產生覆蓋現象。

第二組和第二組是正常狀況,不解釋了。

小結

這就是個人理解,但願你們指正。之後會多分析jquery的實現過程,源碼的細節太難了。

相關文章
相關標籤/搜索