KISSY深入研究(2)——dom-data.js

概述

dom-data子模块隶属DOM模块下,其作用与jQuery中的数据缓存类似,主要是用于在元素上存储数据,其特点是能避免传统方式中因循环引用而引起的内存泄漏风险,同时还可以处理对于全局对象附加数据,限制对全局对象污染的情况。

缘由

之于概述里面所提到内存泄漏如何理解呢?假设我们有以下代码:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Data</title>
</head>
<body>
    <a href="#Foo" onclick="showInfo(this); return false;">I'm Foo!</a>
    <a href="#Bar" onclick="showInfo(this); return false;">I'm Bar!</a>
    <script>
        (function() {
            var peopleDataBase = [
                {
                    'name': 'Foo',
                    'age': 28,
                    'sex': 'male'
                },
                {
                    'name': 'Bar',
                    'age': 19,
                    'sex': 'female'
                }
            ], people = document.getElementsByTagName('a'), i = 0, len = people.length;
            for (; i < len; ++i) {
                peopleDataBase[i]['ref'] = people[i];
                people[i].data = peopleDataBase[i];
            }
        })();
        function showInfo(self) {
            var data = self.data;
            alert('Name: ' + data.name + '\nAge: ' + data.age + '\nSex: ' + data.sex + '\nMsg: ' + data.ref.innerHTML);
        }
    </script>
</body>
</html>

实例目的很简单,每个链接上绑定了数据库中的数据条目,后续会使用到其上的数据,且数据中的ref字段指定了这条数据绑定于哪个链接上。这个简单的例子就会引起内存泄漏,原因是由于链接上直接与数据库中的数据条目做了绑定,同时我们为了在数据库条目中知道这个条目是在何处引用,因此ref字段又指向了这个链接,因此就形成一个循环引用,导致内存泄漏。

memory_leak

这里对于内存泄漏的讨论还是很初步的仅仅包含对象相互引用导致的问题,但是事件注册,闭包使用也会导致内存泄漏的情况,具体可以猛击IE 内存泄露问题,其中包含比较丰富的讨论。

对于概述中提到的第二种情况,也就是全局对象的数据绑定,相信大家都有概念就是尽量不要污染全局对象,尽可能做到无侵入,但是对于众多必须使用的全局数据如何处理呢?那么使用数据缓存的方式同样也能解决。

那么除去一、二种情况的其他情况呢?自然就是普通对象的数据绑定,但是对于这种情况的应用场景实在不常见,这是这里提出一种理念,是处理全局对象数据绑定时候可以借鉴的。

本质

刚刚上述的数据绑定实际上是涉及到了JavaScript中的一个expando的概念,expando可以叫做可扩展对象,主要是动态语言所具有的一个语言特性,即可在运行时动态地增、删、替换其上的任何成员,这与传统静态语言,只能在设计阶段明确声明成员或行为然后编译,有着本质的差别,如果想要更多地了解expando,请猛击这里。那么数据绑定,本质就是对需要绑定数据的元素的expando操作,只是在处理这些元素的expando时,需要考虑到几种情况。

原理

既然直接往元素上附加数据很容易导致内存泄漏,全局对象的数据绑定也有较大风险,那么如何解决这些问题呢?下面就隆重推出KISSY中的数据绑定,KISSY.DOM中data相关方法的原理介绍,方法的具体使用说明可以参看API文档

KISSY.DOM.data方法是数据绑定中的核心方法,遵循KISSY的API设计规范,这个方法同样包含getter和setter两种模式,通过传入参数的个数不同进行操作。这里的数据绑定方式和众多类库相似,使用了缓存机制,当然,与我们之前研究过的KISSY事件模型(1)(2)(3)也有异曲同工之妙。正是基于防止内存泄漏的考虑,我们仅在元素对象本身上附加简单字符串,通过其与缓存中匹配,提取到真正需要的数据,这样就不会产生对象循环引用的潜在威胁。同时在处理全局对象的数据绑定,我们也可以考虑仅仅在全局对象中附加这样的简单字符串,使用时同样通过数据缓存获取(设置)真正的数据,这样我们仅仅会有一个变量侵入全局对象。综上所述,我们可以把这里提到的缓存分为三大类处理,一类就是Node节点的数据缓存,另一类是全局对象的数据缓存,最后一类就是普通对象数据缓存。

Node节点数据缓存的结构如下图所示:

data_cache

对于Node节点数据缓存,我们采用闭包中的一个局部变量作为缓存变量,这样保证了缓存只可由特定的方法提供访问、修改。那么Node节点与缓存中数据相连接的桥梁就是expando,其key是以“_ks_data_”开头的每个KISSY库唯一的值,value就是每个元素所具有的唯一id。这里的key仅仅是节点建立与缓存的桥梁,实际用于在缓存中寻找数据的每个节点唯一的id,在缓存中检索数据时,它便成为索引的key。正是由于这里的expando是简单的字符串,因此不存在对象循环引用的问题,从而避免了内存泄漏的危险。

全局对象数据缓存结构:

win_data_cache

全局对象数据缓存结构与Node节点的十分类似,也是采用闭包中的一个局部变量作为缓存变量存储数据。但是对于普通对象的数据绑定便没有采用单独的缓存变量,原因是由于普通对象本身就具备一个良好的缓存特性,其实全局对象也是相同的,只是考虑到避免全局对象污染才采取了类似Node节点的数据缓存思想,使用具备变量做数据缓存。因此可以详见的是全局对象数据缓存实际是结合了普通对象数据缓存和Node节点数据缓存的方式,在缓存中使用每个KISSY库唯一的expando作为建立访问的桥梁。由于普通对象中可能还有其他很多expando,因此就需要这个唯一的expando标识其为数据缓存,这里全局对象数据也是使用同样的方法,虽然这个缓存对象不存在普通对象那个多个expando,但是依然统一地使用唯一expando开辟缓存空间,保证了代码的统一性。

既然全局对象数据缓存融合了普通对象数据缓存和Node节点数据缓存的特性,那么其实可以讲全局对象缓存实现在Node节点缓存中,由于Node节点缓存中是通过唯一的id来链接Node节点与数据缓存,那么我们同样可以给全局对象设置一个唯一id来表示,这样自然也就建立了唯一对应的数据缓存。只是KISSY的最终实现里面还是采用了普通对象数据缓存的结构,因此有了上图的结构。

实现

原理清楚了,那么现在大概了解一些KISSY.DOM.data(selector, name, data)函数的处理流程,首先根据selector选择元素,对于可能存在的多个元素,getter方法的原则是只返回第一个元素的值,而setter方法的原则则是设置每个元素的值。对于getter方法和setter方法是通过判断是否传入了data参数进行选择的。这里还需要注意的是,embed, object, applet这三个Node节点因为其特殊性,在其上设置数据可能会导致一定的风险,所以统一限制对于这三种标签的数据绑定。

在实现中还有一点儿需要注意的是每个KISSY库都有唯一的expando值,因为元素和缓存都是通过这个值进行链接的,如果页面中不小心再次引入了一个KISSY,相同的expando值会导致其中缓存的关系便会杂糅在一起,产生不可控异常。

总结一下,该函数的大致流程,可用如下图表示:

KISSY.DOM.data

最后还有一个removeData方法,这个方法的作用顾名思义就是移除用data方法增加了的数据。

- EOF -

3 Comments For This Post

  1. 很详尽了,感谢龚浩的整理 :biggrin:

    回复该留言

  2. Kissy系列文章太帅了。

    回复该留言

Leave a Reply

:wink: :twisted: :surprised: :smile: :sad: :rolleyes: :redface: :razz: :question: :neutral: :mrgreen: :mad: :lol: :idea: :exclaim: :evil: :eek: :cry: :cool: :confused: :biggrin: :arrow: