jQuery數據緩存功能的解析及簡單實現


前言

對於jQuery的數據緩存,相信大家都不會陌生,jQuery緩存系統不僅運用於DOM元素動畫、事件等都有用到這個緩存系統。所以在平時實際應用中, 我們經常需要給元素緩存一些數據,並且這些數據往往和DOM元素緊密相關。由於DOM元素(節點)也是對象, 所以我們可以直接擴展DOM元素的屬性,但是如果給DOM元素添加自定義的屬性和過多的數據可能會引起內存泄漏,所以應該要盡量避免這樣做。 因此更好的解決方法是使用一種低耦合的方式讓DOM和緩存數據能夠聯系起來

 另外:對於jQuery.data和jQuery.removeData靜態方法、以及基於這兩個方法的原型擴展方法的介紹和用法就不多說了,可以查看官方API文檔。

實現思路

jQuery提供了一套靈活和強大的緩存方法:

(1)先在jQuery內部創建一個cache對象{}, 來保存緩存數據。 然后往需要進行緩存的DOM節點上擴展一個值為expando的屬性, 這里是”jQuery” + (new Date).getTime()。 注:expando的值等於”jQuery”+當前時間, 元素本身具有這種屬性的可能性很少,所以可以忽略沖突。

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

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

var cache = {
    "uuid1": { // DOM節點1緩存數據,"uuid1"相當於dom1[expando]
        "name1": value1, 
        "name2": value2
    },
    "uuid2": { // DOM節點2緩存數據,“uuid2"相當於dom2[expando]
        "name1": value1, 
        "name2": value2
    }
    // ......
};

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

 

簡單模擬實現

根據以上思路,就可以簡單實現下jQuery.data和jQuery.removeDate的功能了:

(function(window, undefined) {
    var cacheData = {}, // 用來存儲數據的對象
        win = window, // 把window緩存給一個變量
        uuid = 0,
        // 聲明隨機數(8位)
        // 注意+new Date()生成的隨機數是Number類型,與一個空字符串連接后(或使用toString方法轉型后)變成字符串,才可使用slice方法。
        expando = "cacheData" + (+new Date() + "").slice(-8);
        // (+new Date()).toString().slice(-8)等價於expando

    // 寫入緩存
    var data = function(elem, name, value) {
        // 或使用原生方法驗證字符串Object.prototype.toString.call(elem) === "[object String]"
        // 如果elem為字符串
        if (typeof elem === "string") {
            // 如果傳入name參數,則為寫入緩存
            if (name !== undefined) {
                cacheData[elem] = name;
            }

            // 返回緩存數據
            return cacheData[elem];
        // 如果elem為DOM節點
        } else if (typeof elem === "object") {
            var id,
                thisCache;

            // 如果elem不存在expando屬性,則添加一個expando屬性(第一次給元素設置緩存),否則直接獲取已有的expando和id值
            if (!elem[expando]) {
                id = elem[expando] = ++uuid;
                thisCache = cacheData[id] = {};
            } else {
                id = elem[expando];
                thisCache = cacheData[id];
            }

            // 把一個隨機數作為當前緩存對象的一個屬性,利用該隨機數就能找到該緩存對象
            if (!thisCache[expando]) {
                thisCache[expando] = {};
            }

            if (value !== undefined) {
                // 將數據存到緩存對象中
                thisCache[expando][name] = value;
            }

            // 返回DOM元素存儲的數據
            return thisCache[expando][name];
        }
    };

    // 刪除緩存
    var removeData = function(elem, name) {
        // 如果elem為字符串,則直接刪除該屬性值
        if (typeof elem === "string") {
            delete cacheData[elem];
        // 如果key為DOM節點
        } else if (typeof elem === "object") {
            // 如果elem不存在expando屬性,則終止執行,不用刪除緩存
            if (!elem[expando]) {
                return;
            }

            // 檢測對象是否為空
            var isEmptyObject = function(obj) {
                var name;
                for (name in obj) {
                    return false;
                }
                return true;
            }

            removeAttr = function() {
                try {
                    // IE8即標准瀏覽器可以直接使用delete來刪除屬性
                    delete elem[expando];
                } catch (e) {
                    // IE6/IE7使用removeAttribute方法來刪除屬性
                    elem.removeAttribute(expando);
                }
            },

            id = elem[expando];

            if (name) {
                // 只刪除指定的數據
                delete cacheData[id][expando][name];

                // 如果是空對象,id所對應的數據對象全部刪除
                if (isEmptyObject(cacheData[id][expando])) {
                    delete cacheData[id];
                    removeAttr();
                }
            } else {
                // 刪除DOM元素存到緩存中的所有數據
                delete cacheData[id];
                removeAttr();
            }
        }
    };

    // 把data和removeData掛在window全局對象下,這樣在外部也能訪問到這兩個函數
    win.expando = expando;
    win.data = data;
    win.removeData = removeData;

})(window, undefined);

例子:

HTML結構:

<div id="demo" style="height: 100px; width: 100px; background: #ccc; color: #fff; margin: 20px; text-align: center; line-height: 100px;">
    demo
</div>

js代碼:

window.onload = function() {
    // 測試
    var demo = document.getElementById("demo");

    // 寫入緩存
    data(demo, "myName", "hcy");
    console.log(data(demo, "myName")); // hcy

    data(demo, "myBlog", "http://www.cnblogs.com/cyStyle");
    console.log(data(demo, "myBlog")); // http://www.cnblogs.com/cyStyle

    // 刪除DOM元素的某個緩存值
    removeData(demo, "myBlog");
    console.log(data(demo, "myBlog")); // undefined
    console.log(data(demo, "myName")); // hcy
    console.log(demo[expando]); // 1

    // 刪除DOM元素
    removeData(demo);
    console.log(demo[expando]); // undefined
};

firefox下例子結果截圖:

對於上述例子實現jQuery的簡單緩存系統:先給該DOM元素添加一個隨機生成的屬性expando,這個屬性用來存放訪問緩存數據的id值,就好比DOM元素都有一把開啟緩存保險箱的鑰匙,只要有了鑰匙就可以隨時開啟緩存保險箱。 將本來存放到DOM元素中的數據都轉到了緩存中,而DOM元素本身只要存儲一個簡單的屬性就可以了,這樣就可以將由DOM元素引起的內存泄漏(具體會發生什么狀況不知道,大家都這么說~)的風險規避到最小。

 

結語

 糊里糊塗地又到了最后,有一些術語或解釋上可能存在偏差,望各位童鞋指正和給出一些建議;另外,從理論上講, data和removeData方法可以用於任何對象的緩存, 不過如果運用於本地對象或window對象, 會存在內存泄露、循環引用等問題(^_^從網上看到的), 所以一般還是用於DOM節點比較適合,還可以結合事件、動畫對DOM節點進行緩存數據的操作。ps:cache真的很重要!需要慢慢體會~


免責聲明!

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



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