• 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;  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真的很重要!需要慢慢体会~

  • 相关阅读:
    使用dataInput:DescriptionViewer对输入的数据进行校验
    Nunit2.6.2调试.net4类库
    使用WCF RIA服务支持ASP.NET验证
    偶遇 Lc.exe已退出代码为1
    android程序连接后端web service时,提示:Permission denied
    设置XP系统的自动登录
    DomainDataSource的自动刷新
    安装CentOS时,显示 NET:Registered protocol family 2
    清除SqlServer2008的日志
    http://www.cnblogs.com/KnightsWarrior/archive/2010/08/27/1809739.html(博客主)
  • 原文地址:https://www.cnblogs.com/cyStyle/p/3101492.html
Copyright © 2020-2023  润新知