jQuery.data的是jQuery的數據緩存系統


jQuery.Data源碼

jQuery.data的是jQuery的數據緩存系統

jQuery.data的是jQuery的數據緩存系統。它的主要作用就是為普通對象或者DOM元素添加數據。

1 內部存儲原理

image

這個原理很簡單,原本要添加在DOM元素本身的數據,現在被集中的存儲在cache集合中。它們之間靠一個從1開始的數字鍵來聯系着。這樣DOM元素就不會像以前那么笨重了,更不會出現以前那種循環引用而引起的內存泄漏。現在DOM只需要保存好這個數字鍵值即可。這個屬性值被保存在DOM元素的一個屬性里,該屬性名是由jQuery.expando生成的。

2 Data構造函數

Object.defineProperty( this.cache = {}, 0, {
    get: function() {
        return {};
    }
});

首先來看Object.defineProperty函數,它的作用為:將屬性添加到對象,或修改現有屬性的特性。那我們先來看下ECMAScript5中的屬性。

2.1 ECMAScript5中的屬性

ECMAScript5中有兩種屬性:數據屬性和訪問器屬性。

2.1.1 數據屬性:

數據屬性包含一個數據值的位置,在這個位置可以讀取和寫入值。有4種特性用來限制其行為。

①[[configurable]]

能否通過delete刪除屬性,能否修改屬性的特性,能否把屬性改為訪問器屬性。默認為true。

復制代碼
var  obj={name:"abc"};
Object.defineProperty(obj,"name",{
    configurable:false
});
console.log(obj.name);
delete obj.name;
console.log(obj.name);
Object.defineProperty(obj,"name",{
    configurable:true
});
復制代碼
image

注意,一旦將 configurable 設為false,就再也設置不回去了。而且還會報錯。

②[[enumerable]]

在for-in循環是否能獲取到屬性。默認為true。ECMAScript規定,由程序員定義的屬性的該特性都為true。

③[[writable]]

能否修改屬性的值。默認為true。

復制代碼
var  obj={name:"abc"};
Object.defineProperty(obj,"name",{
    writable:false
});
console.log(obj.name);
obj.name="111";
console.log(obj.name);
復制代碼
image

④[[value]]

屬性值所在的地方。讀取屬性值的時候獲取的就是它,寫屬性值的時候也是往這里寫。

復制代碼
var  obj={name:"abc"};
console.log(obj.name);
Object.defineProperty(obj,"name",{
    value:111
});
console.log(obj.name);
復制代碼
image

2.1.2訪問器屬性

訪問器屬性不包含實際的屬性值,它包含兩個函數(getter和setter)

①[[get]]

讀取屬性時調用的函數。默認為undefined

②[[set]]

寫入屬性時調用的函數。默認為undefined

復制代碼
var obj={};
Object.defineProperties(obj,{
    name:{
        get:function () {
            return obj["_name"];
        },
        set:function (name) {
            if(name != "C#"){
                obj["_name"]=name;
            }
        }
    },
    label:{
        get:function () {
            return "你不能改變我!!!";
        }
    }
});
obj.name="JavaScript";
console.log(obj.name);
obj.label="我偏要改變你!!!";
console.log(obj.label);
復制代碼
image

上面給cache的屬性0,只給了get屬性。所以不能為該屬性賦值。只能獲取。

this.expando = jQuery.expando + Math.random();

這個expando就是用來為DOM元素或者對象存儲在cache中的鍵的。即它將作為DOM元素的一個屬性被存儲這。

b4722170-1292-4c0e-bb79-8665339ad935

紅色畫框內的屬性名就是由上面的語句生成的,指明div1的屬性存儲在cache的1屬性中。

Data.uid = 1;
該屬性表示cache的屬性將從1開始自增。因為0已經被這個凍結的空JSON占用了,所以從1開始。我們下次再為某DOM元素添加屬性時,它將被保存在cache的2屬性中。

3 允許的添加屬性的元素

Data.accepts = function( owner ) {
    return owner.nodeType ?
        owner.nodeType === 1 || owner.nodeType === 9 : true;
};

這段代碼寫的非常干練。表示如果owner是DOM元素則只有ELEMENT_NODE和DOCUMENT_NODE兩種元素能添加屬性,如果owner是一個對象,則都可以添加屬性。

4 原型屬性

4.1 key: function( owner ) {

if ( !Data.accepts( owner ) ) {
    return 0;
}

如果owner不能被添加data,則返回cache的第0個元素。

unlock = owner[ this.expando ];

這里獲取的就是

b5699b61-ea45-4a9a-b45c-ebd3727f28af

如果能從owner中找到這個屬性,則說明以前為它添加過值,也就是說,它已經擁有了一個在cache中的key。

復制代碼
if ( !unlock ) {
    unlock = Data.uid++;
    try {
        descriptor[ this.expando ] = { value: unlock};
        Object.defineProperties( owner, descriptor );
    } catch ( e ) {
        descriptor[ this.expando ] = unlock;
        jQuery.extend( owner, descriptor );
    }
}
復制代碼

如果找不到,則說明是第一次為owner添加屬性,則要創建一個key。這個key就是在Data的uid的基礎上加1.Data的uid是一個靜態屬性。這樣就能記錄前一個元素的key是多少,這次的key又應該是多少。try塊里面為owner(即DOM元素或者對象)添加它在cache中的索引。這里會出現兼容性的問題。在Android系統<4時會出現安全問題,因此jQuery使用extend靜態方法將descriptor擴展到owner上面。

if ( !this.cache[ unlock ] ) {
    this.cache[ unlock ] = {};
}

這里為owner對應在cache中的key賦值一個Object。

這個的key方法的作用就是為DOM元素創建屬性,為cache中對應的key賦值(空對象)。

image

key返回的是這個unlock,即owner對象在cache中對應的key。

4.2 set: function( owner, data, value ) {

unlock = this.key( owner ),
cache = this.cache[ unlock ];
第一句不用解釋,第二句或者cachekey值。以上圖所示,則這里的cache目前還是{},因為在此之前還沒有為div1添加過屬性。
if ( typeof data === "string" ) {
    cache[ data ] = value;

我們下面的代碼走的就是這里:

$("#div1").data("name","div1");

但是我們有很多屬性要設置的時候,

$("#div1").data({name:"div1",from:"0",to:"100"});

jQuery的處理方式是這樣的:

復制代碼
// 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 ];
        }
    }
}
復制代碼

其實這里個人以為不用再判斷了,因為extend里面也是用for循環將data的屬性擴展到cache中的。

4.3 get: function( owner, key ) {

var cache = this.cache[ this.key( owner ) ];
return key === undefined ? cache : cache[ key ];

獲取屬性,代碼非常簡單。如果key不存在則返回cache對象。

4.4 access: function( owner, key, value ) {

這里對get和set方法的統一訪問。

4.5 remove: function( owner, key ) {

unlock = this.key( owner ),
cache = this.cache[ unlock ];

獲取owner的在cache中的數據對象,this.key返回的是owner在cache中的key。

if ( key === undefined ) {
    this.cache[ unlock ] = {};

如果不指定要刪除那個屬性的話,jQuery會刪除owner所有的數據屬性。

否則再判斷key是不是數組,

if ( jQuery.isArray( key ) ) {
    name = key.concat( key.map( jQuery.camelCase ) );
}
是數組的話,將key數組和用key的每一項轉駝峰后的數組合並,即:
$("#div1").remove(["one_key","two_key"]);

經過上面的代碼,key為變為["one_key","two_key","oneKey","twoKey"]。后面會將這4個屬性都刪除掉。

那如果key不是數組:

復制代碼
camel = jQuery.camelCase( key );
if ( key in cache ) {
    name = [ key, camel ];
} else {
    name = camel;
    name = name in cache ?
        [ name ] : ( name.match( core_rnotwhite ) || [] );
}
復制代碼

同樣先輸轉駝峰,然后判斷key是否存在,不存在則判斷key的駝峰形式是否存在,后面的正則表達式用於去除駝峰形式前后的空格。

15beac62-c2e7-453d-8cf6-a4814dbb9854

當這些情況過濾完之后,進行刪除操作:

i = name.length;
while ( i-- ) {
    delete cache[ name[ i ] ];
}

在while循環里面使用delete進行刪除。

4.6 hasData: function( owner ) {

cache中是否擁有woner的數據對象。

4.7 discard: function( owner ) {

刪除cache中的woner的數據對象。

image

5 創建兩個私有的cache

data_user = new Data();
data_priv = new Data();
所以,我們在jQuery的外面不能直接拿到這個cache。因為它是jQuery的局部變量。data_user供開發人員使用,data_priv供jQuery內部使用。

6 創建對外接口(工具方法和原型方法)

由於Data構造器是jQuery私有的,我們在外面不能訪問到,所以前面的那些方法,我們也不能直接訪問,jQuery在這里,給我們提供了一些接口。來操作data_user,為DOM元素和Object進行屬性操作。工具方法非常簡單只是對data_user方法的封裝而已。我們主要看下原型方法。

jQuery.fn.extend({
    data: function( key, value ) {

設值和取值都會進入上面的方法。

這里有這樣一個思想,如果是設值的時候,則給選集中所有的選項設值,如果獲取值的時候,只獲取第一個選項的值。

復制代碼
if ( key === undefined ) {
    if ( this.length ) {
        data = data_user.get( elem );
        if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
            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 ] );
                }
            }
            data_priv.set( elem, "hasDataAttrs", true );
        }
    }
    return data;
}
復制代碼

在第3行,已經取到cache中elem對應的屬性了,下面jQuery由將elem的attributes里的所有屬性添加到elem的    。

hasDataAttrs屬性是我們自己添加,因為下面這段代碼只需要執行一次即可。代碼第6行,獲取elem所有的屬性;

a7b4953a-c10f-4d0a-a507-6542fe126ee4

這個NamedNodeMap類型,我們平時很少直接用它,它和NodeList,HTMLCollection一樣都是“動態”的。NamedNodeMap集合中的每一項都是Attr類型,Attr對象有3個屬性:name,value和specified。

在下面的for循環里面,就檢測這個name屬性中是否含有"data-"前綴。有的話就去掉它,並且將剩余部分轉為駝峰形式。

如果key不存在,則表示獲取選集中第一個選項的所有屬性值。elem表示第一個選項。

if ( typeof key === "object" ) {
    return this.each(function() {
        data_user.set( this, key );
    });
}
對應這種形式:
$("#div1").remove(["one_key","two_key"]);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM