深刻jQuery中的data()

引入

  data函數在jQuery中看起來很不起眼, 就像沙灘上一顆平凡的沙子, 但仔細一瞅, 卻驚訝的發現data是jQuery中無比重要的一環, 甚至jQuery中各類事件都基於此。javascript

data有什麼做用?

  在咱們平時js編碼過程當中,咱們常常會向DOM元素中添加各類自定義屬性,這樣有一個弊端。java

  1  假設咱們在DOM元素中添加了一個屬性,這個屬性指向了某個js對象。 dom1.ele = jsObjnode

  2  當這個js對象發揮完做用後,咱們已經用不到他了。這時候按理說應該把這個js變量清空,釋放內存。你們都知道,若是一個js對象不存在任何外在引用的話,解釋器會自動將其在內存中刪除,這也是javascript相對於c++等手動管理內存的程序的優勢。jquery

  3  可是這時候問題來了,由於DOM元素引用了這個js對象,儘管這個js對象已經沒有存在的意義了,可是解釋器是不會把他刪除的。若是想要把其刪除,咱們可能須要將DOM元素的這個屬性設置爲null。c++

  4  咱們編寫了這麼多的代碼,哪裏能把 每一個js對象是否是被DOM元素引用了都記住啊?git

  5  並且,假如DOM元素與js對象之間相互循環引用,根本就沒法刪除! 這就是內存泄漏github

  6  因此,爲了不這種狀況的發生,咱們要儘可能避免 引用數據(這裏的引用數據能夠說是javascript對象) 直接依附在DOM對象上。緩存

  7  data就是用來搞定以上問題的方法。app

data是如何搞定以上問題的?

首先來講一說jQuery中Data實現的大致思路:

  1  首先咱們建立一個數據緩存池,這個緩存池專門用來存儲  向 DOM對象或者jQuery對象附加的額外數據。less

  2  當咱們要向DOM對象或者jQuery對象附加額外數據的時候,咱們附加的數據實際上是保存於這個緩存池中

  3  DOM對象或者jQuery對象生成一個額外屬性,這個屬性保存了 附加數據在緩存池中的‘門牌號’(位置或者索引)

  4  當咱們訪問DOM對象或者jQuery對象的附加數據時,其實是先取得其附加數據的門牌號,而後找到緩存池中對應門牌號的數據,進行操做。

大致思路講完,那麼來分析一下具體思路:

在jQuery中,有一個Data構造函數,每當運行這個構造函數時,就會生成一個實例。jQuery默認會自動生成兩個Data實例:

  var dataPriv = new Data()   jQuery私有的,咱們儘可能不要對這個實例進行操做。

  var dataUser = new Data()   這個就是服務於用戶了,咱們使用data()方法都是對這個實例進行操做。

全部的Data實例都有如下屬性:

  expando:  值爲字符串類型,每一個Data實例的expando屬性的值都不相同,用來區分不一樣的Data實例,相似於id的做用,expando的值就是上文中的額外屬性

  uid:   這就是上文中的門牌號,初始爲1,隨着不一樣對象的附加數據的加入,自增加。

  cache : 一個對象 {} ,這就是緩存池了。

來個實例:

$(document.body).data('aaa', 'value-aaa')
console.dir(document.body)

body對象有一個名爲jquer210023......的額外屬性

  這個屬性的名稱就是dataUser的expando的值

  這個屬性的值就是門牌號

 

總結: data實際上就是對js對象或者DOM對象的額外屬性作了一個集中的管理。對於那些不會產生內存泄漏的額外數據,咱們也能夠直接向js對象或者DOM對象附加。

 

好,理清楚上面的關係後,咱們再來看一下源碼:

define([
    "../core",
    "../var/rnotwhite",
    "./accepts"
], function( jQuery, rnotwhite ) {

function Data() {
    // Support: Android<4,
    // Old WebKit does not have Object.preventExtensions/freeze method,
    // return new empty object instead with no [[set]] accessor
    Object.defineProperty( this.cache = {}, 0, {
        get: function() {
            return {};
        }
    });
    //  jQuery.expando = "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ) expando是一個jQuery的惟一標示
    // 格式是:'jQuery\\d*'  也就是'jQuery'+ 多個數字。這裏爲啥要搞得這麼麻煩呢?
    // 應由於咱們可能會建立多個Data對象,爲了保證每一個Data對象的expando屬性的值不相等,因此這麼搞
    this.expando = jQuery.expando + Math.random();
}

Data.uid = 1;  // Data函數的屬性,'靜態屬性'
Data.accepts = jQuery.acceptData;

Data.prototype = {
    key: function( owner ) {
        // We can accept data for non-element nodes in modern browsers,
        // but we should not, see #8335.
        // Always return the key for a frozen object.
        // 若owner在該緩存池中存在對應的緩存對象,則返回混存對象的key(是一個數字),
        // 若owner在該緩存池中不存在對應的緩存對象,則在緩存池中爲其建立一個緩存對象,並返回該緩存對象的key
        if ( !Data.accepts( owner ) ) {
            return 0;
        }

        var descriptor = {},
            // Check if the owner object already has a cache key
            // 檢查owner對象在該緩存池中是否存在緩存
            unlock = owner[ this.expando ];  // 是一個數字,用來做爲緩存池中緩存對象的key

        // If not, create one
        // 若是沒有,則建立一個
        if ( !unlock ) {
            unlock = Data.uid++;

            // Secure it in a non-enumerable, non-writable property
            // 給owner附加一個屬性  owner[this.expando] = unlock ,而且該屬性不能被枚舉,
            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 );
            }
        }

        // Ensure the cache object
        // 確保owner對應的緩存對象已存在
        if ( !this.cache[ unlock ] ) {
            this.cache[ unlock ] = {};
        }
        //  返回unlock
        return unlock;
    },
    set: function( owner, data, value ) {
        // 設置owner對應的緩存對象
        var prop,
            // There may be an unlock assigned to this node,
            // if there is no entry for this "owner", create one inline
            // and set the unlock as though an owner entry had always existed
            unlock = this.key( owner ),  // 獲取owner的對應的緩存對象在緩存池中的key(這裏的key,是鍵值對中的鍵的意思)
            cache = this.cache[ unlock ];  // 獲取owner所對應的緩存對象

        // Handle: [ owner, key, value ] args
        // 根據傳入參數的個數以及類型實現重載
        if ( typeof data === "string" ) {
            cache[ data ] = value;

        // Handle: [ owner, { properties } ] args
        } else {
            // Fresh assignments by object are shallow copied
            if ( jQuery.isEmptyObject( cache ) ) {
                jQuery.extend( this.cache[ unlock ], data );
            // Otherwise, copy the properties one-by-one to the cache object
            } else {
                for ( prop in data ) {
                    cache[ prop ] = data[ prop ];
                }
            }
        }
        // 返回緩存對象
        return cache;
    },
    get: function( owner, key ) {
        // 獲取owner對象的名爲key的屬性值
        // owner:是一個對象(能夠是jQuery對象也能夠是DOM對象)   key: 屬性名
        // Either a valid cache is found, or will be created.
        // New caches will be created and the unlock returned,
        // allowing direct access to the newly created
        // empty data object. A valid owner object must be provided.

        var cache = this.cache[ this.key( owner ) ]; //  owner的緩存對象

        return key === undefined ? cache : cache[ key ];  // 沒指定key的話就返回整個緩存對象,若指定了key則返回在該緩存對象的key屬性的值
    },
    access: function( owner, key, value ) {
        var stored;
        // In cases where either:
        //
        //   1. No key was specified   沒有指定key
        //   2. A string key was specified, but no value provided  指定了字符串格式的key,但沒有指定value
        //
        // Take the "read" path and allow the get method to determine
        // which value to return, respectively either:
        //
        //   1. The entire cache object  整個緩存對象
        //   2. The data stored at the key  緩存對象中某個鍵的值
        //
        if ( key === undefined || // 沒有指定key或者指定了字符串格式的key,但沒有指定value
                ((key && typeof key === "string") && value === undefined) ) {

            // 沒有指定key:獲取整個緩存對象
            // 指定了字符串格式的key,但沒有指定value: 獲取緩存對象中key的值
            stored = this.get( owner, key );


            return stored !== undefined ?
                stored : this.get( owner, jQuery.camelCase(key) );
        }

        // [*]When the key is not a string, or both a key and value
        // are specified, set or extend (existing objects) with either:
        // 當key不是一個字符串,或者key和value都指定了,就會根據狀況進行設置或者擴展
        //
        //   1. An object of properties
        //   2. A key and value
        //
        this.set( owner, key, value );

        // Since the "set" path can have two possible entry points
        // return the expected data based on which path was taken[*]
        return value !== undefined ? value : key;
    },
    remove: function( owner, key ) {
        // 清空owner對應的緩存對象,或者移除緩存對象中的某個鍵值對
        var i, name, camel,
            unlock = this.key( owner ),
            cache = this.cache[ unlock ];
        // 若是沒有指定key,則清空緩存對象
        if ( key === undefined ) {
            this.cache[ unlock ] = {};

        } else {
            // Support array or space separated string of keys
            if ( jQuery.isArray( key ) ) {
                // If "name" is an array of keys...
                // When data is initially created, via ("key", "val") signature,
                // keys will be converted to camelCase.
                // Since there is no way to tell _how_ a key was added, remove
                // both plain key and camelCase key. #12786
                // This will only penalize the array argument path.
                name = key.concat( key.map( jQuery.camelCase ) );
            } else {
                camel = jQuery.camelCase( key );
                // Try the string as a key before any manipulation
                if ( key in cache ) {
                    name = [ key, camel ];
                } else {
                    // If a key with the spaces exists, use it.
                    // Otherwise, create an array by matching non-whitespace
                    name = camel;
                    name = name in cache ?
                        [ name ] : ( name.match( rnotwhite ) || [] );
                }
            }

            i = name.length;
            while ( i-- ) {
                delete cache[ name[ i ] ];
            }
        }
    },
    hasData: function( owner ) {
        // 檢查owner在該緩存池中是否存在緩存對象
        return !jQuery.isEmptyObject(
            this.cache[ owner[ this.expando ] ] || {}
        );
    },
    discard: function( owner ) {
        if ( owner[ this.expando ] ) {
            delete this.cache[ owner[ this.expando ] ];
        }
    }
};

return Data;
});
Data構造函數源碼解析

 

可能會有同窗問道:若是我想對dataPriv進行操做該如何?

請看源碼:

jQuery.extend({
    hasData: function( elem ) {
        return dataUser.hasData( elem ) || dataPriv.hasData( elem );
    },

    data: function( elem, name, data ) {
        return dataUser.access( elem, name, data );
    },

    removeData: function( elem, name ) {
        dataUser.remove( elem, name );
    },

    // TODO: Now that all calls to _data and _removeData have been replaced
    // with direct calls to dataPriv methods, these can be deprecated.
    _data: function( elem, name, data ) {
        return dataPriv.access( elem, name, data );
    },

    _removeData: function( elem, name ) {
        dataPriv.remove( elem, name );
    }
});

經過源碼,咱們能夠看出:

  jQuery.data() jQuery.remove()都是對dataUser進行操做,而jQuery._data() jQuery._remove()都是對dataPriv進行操做。

 

理解jQuery.data(ele,name,data) 與 jQuery().data(key,value)的不一樣。

  經過上面的源碼,咱們能夠看到jQuery.data(ele,name,data)是對ele元素附加數據。

  而jQuery().data(key,value)則會爲jQuery對象中的全部DOM對象分別附加數據

來看源碼(刪減了部分):

    jQuery.fn.extend({
    data: function( key, value ) {
        var i, name, data,
            elem = this[ 0 ],
            attrs = elem && elem.attributes;return access( this, function( value ) {
            var data,
                camelKey = jQuery.camelCase( key );

// 從這裏能夠看出,爲jQuery對象中的每一個DOM元素分別附加數據 this.each(function() { // First, attempt to store a copy or reference of any // data that might've been store with a camelCased key. var data = dataUser.get( this, camelKey ); // For HTML5 data-* attribute interop, we have to // store property names with dashes in a camelCase form. // This might not apply to all properties...* dataUser.set( this, camelKey, value ); // *... In the case of properties that might _actually_ // have dashes, we need to also store a copy of that // unchanged property. if ( key.indexOf("-") !== -1 && data !== undefined ) { dataUser.set( this, key, value ); } }); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each(function() { dataUser.remove( this, key ); }); } });

 -----------------------------------------------分隔線---------------------------------------------------

上文中的全部源碼:爲jQuery.1.12  

相關文章
相關標籤/搜索