jQuery.data() 的實現方式

下面將分三個部分分析其實現方式: 
    1. 用name和value爲對象附加數據;即傳入三個參數,第一個參數爲需要附加數據的對象,第二個參數爲數據的名稱,第三個參數爲數據的值。當然,只是獲取值的話,也可以不傳入第三個參數。 
    2. 用另一個對象爲對象附加數據;即傳入兩個參數,第一個參數爲需要附加的數據對象(我們稱之爲“obj”),第二個參數也是一個對象(我們稱之爲“another”);“another”中包含的鍵值對將會被複制到 “obj” 的數據緩存(我們稱之爲“cache”)中。 

    3. 爲 DOM Element 附加數據;DOM Element 也是一種 Object ,但 IE6、IE7 對直接附加在 DOM Element 上的對象的垃圾回收存在問題;因此我們將這些數據存放在全局緩存(我們稱之爲“globalCache”)中,即 “globalCache” 包含了多個 DOM Element 的 “cache”,並在 DOM Element 上添加一個屬性,存放 “cache” 對應的 uid 。

@NOTE: 對於使用window或iframe等BOM對象存儲數據而言,需要使用第2種情況將其看成普通對象進行存儲數據,而非DOM對象,這一點參照淘寶的開源前端類庫KISSY DOM.data方法;

用name和value爲對象附加數據

    使用 jQuery.data() 爲普通對象附加數據時,其本質是將一個 “cache” 附加到了對象上,並使用了一個特殊的屬性名稱。 
    存放數據的 “cache” 也是一個 object,我們爲 “obj” 附加的數據實際上成爲了 “cache” 的值。而 “cache” 又是 “obj” 的一個屬性,在 jQuery 1.6中,這個屬性的名稱是 “jQuery16”加上一個隨機數(如下面提到的 “jQuery16018518865841457738” )。 
    具體關係如下:

         obj {

            'jQuery16018518865841457738' : { some object you need to store }

          }

    我們可以用下面的代碼來測試 jQuery.data() 的功能: 

<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
var obj = {};
$.data(obj, 'name', 'value');
 document.write("$.data(obj, 'name') = " + $.data(obj, 'name') + '<br />');
 for (var key in obj) {
	document.write("obj." + key + '.name = ' + obj[key].name);
}
</script>

 顯示結果爲:

$.data(obj, 'name') = value
obj.jQuery16018518865841457738.name = value

在這段代碼中,我們首先在 “obj” 上附加了一個屬性(名稱爲“name”,值爲“value”),然後通過 $.data(obj, 'name') 來獲取所附加的數據。爲了深入瞭解其中的實現機制,我們有使用了一個循環來獲取 “obj” 的屬性,實際上是取出了在 “obj” 上附加的 “cache”對象。 

    可以看到,jQuery.data() 實際上爲 “obj” 附加到了名爲 “jQuery16018518865841457738” (這個名稱是隨機的)的對象,也就是 “cache” 上。用 jquery.data() 方式爲對象附加的屬性實際上成爲了這個 “cache” 的屬性。 

    我們可以用下面的代碼實現類似的功能: 


$ = function() {
    var expando = "jQuery" + ("1.6" + Math.random()).replace(/\D/g, '');

    function getData(cache, name) {
        return cache[name];
    }

    function setData(cache, name, value) {
        cache[name] = value;
    }

    function getCache(obj) {
        obj[expando] = obj[expando] || {};
        return obj[expando];
    }

    return {
        data : function(obj, name, value) {
            var cache = getCache(obj);

            if (value === undefined) {
                return getData(cache, name);
            } else {
                setData(cache, name, value);
            }
        }
    }
}();


function 中的第一行代碼定義了 “expando” ,即 "jQuery1.6" 加上一個隨機數(0.xxxx),並將其中非數字的部分去掉;這種格式將在jQuery的其他地方用到,這裏不做探討;只需要知道這是一個特殊的名稱,並且可以用於標識不同的頁面(比如不同 iframe 中的 “expando” 就會有所不同)。 

    接下來定義了獲取數據的函數 getData(), 即從 “cache” 中獲取一個屬性;實際上也就是返回 cache[name] 。 
    然後是 setData() 函數,用於設置 “cache” 的屬性;實際上也就是設置 cache[name] 的值。 
    之後是 getCache() , 獲取 “obj” 上的 “cache”,即 obj[expando];如果 obj[expando] 爲空,則進行初始化。 
    最後公開了 data 方法,先根據傳入的 “obj”,獲取附加在 “obj” 上的 “cache”; 當傳入兩個參數時,調用 getData()方法;當傳入三個參數時,則調用 setData() 方法。 


用另一個對象爲對象附加數據


     除了以提供 name 和 value 的方式進行賦值,我們還可以直接傳入另一個對象( “another” )作爲參數。這種情況下,“another” 的屬性名稱和屬性值將被視爲多個鍵值對,從中提取的 “name” 和 “value” 都會被複制到目標對象的緩存中。 

     功能測試代碼如下: 

<script type="text/javascript" src="jquery.js"></script>
<script>
var obj = {};
$.data(obj, {name1: 'value1', name2: 'value2'});

document.write("$.data(obj, 'name1') = " + $.data(obj, 'name1')  + '<br />' );
document.write("$.data(obj, 'name2') = " + $.data(obj, 'name2') + '<br />');

for (var key in obj) {
	document.write("obj." + key + '.name1 = ' + obj[key].name1 + '<br />');
	document.write("obj." + key + '.name2 = ' + obj[key].name2);
}
</script>

    顯示結果如下:
$.data(obj, 'name1') = value1
$.data(obj, 'name2') = value2
obj.jQuery1600233050178663064.name1 = value1
obj.jQuery1600233050178663064.name2 = value2

上面的測試代碼中,我們先將一個帶有兩個鍵值對的 “another” 對象傳入,然後分別用 $.data(obj, 'name1') 和 $.data(obj, 'name2') 獲取附加的數據;同樣,爲了深入瞭解其中的機制,我們通過遍歷 “obj” 的方式取出了隱藏的 “cache” 對象,並獲得了 “cache” 對象的 “name1” 屬性和 “name2” 屬性的值。 

    可以看到,jQuery.data() 實際上爲 “obj” 附加了名爲 “obj.jQuery1600233050178663064” 的對象,也就是 “cache” 上。用 jquery.data() 方式傳入的鍵值對都被複制到了 “cache” 中。 

    我們可以用下面的代碼實現類似的功能:


$ = function() {
    // Other codes ...

    function setDataWithObject(cache, another) {
        for (var name in another) {
            cache[name] = another[name];
        }
    }

    // Other codes ...

    return {
        data : function(obj, name, value) {
            var cache = getCache(obj);

            if (name instanceof Object) {
                setDataWithObject(cache, name)
            } else if (value === undefined) {
                return getData(cache, name);
            } else {
                setData(cache, name, value);
            }
        }
    }
}();

這段代碼是在之前的代碼的基礎上進行修改的。首先增加了內部函數 setDataWithObject() ,這個函數的實現是遍歷 “another” 的屬性,並複製到 “cache” 中。 
    然後,在對外開放的 data 函數中,先判斷傳入的第二個參數的名稱,如果這個參數是一個 Object 類型的實例,則調用 setDataWithObject() 方法。


爲 DOM Element 附加數據


    由於 DOM Element 也是一種 Object,因此之前的方式也可以爲 DOM Element 賦值;但考慮到 IE6、IE7 中垃圾回收的問題(不能有效回收 DOM Element 上附加的對象引用),jQuery採用了與普通對象有所不同的方式附加數據。 

    測試代碼如下:

<div id="div_test" />

<script type="text/javascript" src="data.js"></script>
<script>
window.onload = function() {
	div = document.getElementById('div_test');
	$.data(div, 'name', 'value');
	document.write($.data(div, 'name'));
}
</script>

顯示結果如下:
value

測試代碼中,首先通過 document.getElementById 方法獲取了一個 DOM Element (當然,也可以用 jQuery 的選擇器),然後在這個 DOM Element 上附加了一個屬性,隨後就從 DOM Element 上取出了附加的屬性並輸出。 

    因爲考慮到 IE6、IE7 對 DOM Element 上的對象引用的垃圾回收存在問題,我們不會直接在 DOM Element 上附加對象;而是使用全局cache,並在 DOM Element 上附加一個 uid。 

    實現方式如下: 

$ = function() {
    var expando = "jQuery" + ("1.6" + Math.random()).replace(/\D/g, '');
    var globalCache = {};
    var uuid = 0;

    // Other codes ...

    function getCache(obj) {
        if (obj.nodeType) {
            var id = obj[expando] = obj[expando] || ++uuid;
            globalCache[id] = globalCache[id] || {};
            return globalCache[id];
        } else {
            obj[expando] = obj[expando] || {};
            return obj[expando];
        }
    }

    // Other codes ...
}();

這段代碼與之前的代碼相比,增加了 globalCache 和 uuid,並修改了 getCache() 方法。 

    globalCache 對象用於存放附加到 DOM Element 上的 “cache”,可以視爲 “cache” 的“容器”。uuid 表示 “cache” 對應的唯一標識,是唯一且自增長的。uuid 或被存放在 DOM Element 的 “expando” 屬性中。 
    getCache() 函數中增加了一個判斷,即 “obj” 具有 “nodeType” 屬性,就認爲這是一個 DOM Element;這種情況下,就先取出附加在 “obj” 上的 id ,即 obj[expando] ;如果 obj[expando] 未定義,則先用 ++uuid 對其進行初始化;取出 id 之後,就到 globalCache 中找到對應的 “cache” ,即 globalCache[id], 並返回。 

    到此爲止,jQuery.data() 函數的實現就介紹完了;但是,這裏還有一個需要思考的問題:爲什不都統一用 “globalCache” 存儲,而要將 “cache” 直接附加到普通對象上?我認爲這應該是一種性能優化的方式,畢竟少一個引用的層次,存取速度應該會略快一些。 jQuery 中這刻意優化的地方非常多,在許多原本可以統一處理的對方都進行了特殊處理。但這在一定程度上,也造成了閱讀源碼的障礙。當然這是作者(及其他代碼貢獻者)本身的編程哲學,這裏就不加評論了。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章