javascript代码实例教程-jQuery源码解读 - 数据缓存系统:jQuery.data

发布时间:2019-01-24 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了javascript代码实例教程-jQuery源码解读 - 数据缓存系统:jQuery.data脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
小宝典致力于为广大程序猿(媛)提供高品质的代码服务,请大家多多光顾小站,小宝典在此谢过。 jQuery在1.2后引入jquery.data(数据缓存系统),主要的作用是让一组自定义的数据可以DOM元素相关联——浅显的说:就是让一个对象和一组数据一对一的关联。

 

一组和Element相关的数据如何关联着这个Element一直是web前端的大姨妈,而最初的jQuery事件系统照搬Dean Edwards的addEvent.js:将回调挂载在EventTarget上,这样下来,循环引用是不可忽视的问题。而在web前端中,数据和DOM的关系太过基情和紧张,于是jQuery在1.2中,正式缔造了jQuery.data,就是为了解决这段孽缘:自定义数据和DOM进行关联。

 

文中所说的Element主要是指数据挂载所关联的target(目标),并不局限于Element对象。

 

本文原创于linkFly,原文地址。

 

这篇文章主要分为以下知识

 

jQuery.data模型

jQuery.1.x中jQuery.data实现

jQuery.2.x中jQuery.data实现

其他实现

jQuery.data模型

模型

凡存在,皆真理——任何一样事物的存在必然有其存在的理由,于我们的角度来说,这叫需求。

 

一组数据,如何与DOM相关联一直是web前端的痛处,因为浏览器的兼容性等因素。最初的jQuery事件系统照搬Dean Edwards的addEvent.js:将回调挂载在EventTarget上,这样下来,循环引用是不可忽视的问题,它把事件的回调都放在相应的EventTarget上,当回调中再引用EventTarget的时候,会造成循环引用。于是缔造了jQuery.data,在jQuery.event中通过jQuery.data挂载回调函数,这样解决了回调函数的循环引用,随时时间的推移,jQuery.data应用越来越广,例如后来的jQuery.queue。

 

首先我们要搞清楚jQuery.data解决的需求,有一组和DOM相关/描述Element的数据,如何存放和挂载呢?可能有人是这样的:

 

使用attributes

HTML

 

<p id="demo" userData="linkFly"></p>

javascript:

 

 

(function () {

            VAR demo = document.getElementById(&#39;demo');

            console.LOG(demo.getAttribute('userData'));

})();

使用HTML5的dataset

HTML:

 

<p id="demo2" data-user="linkFly"></p>

javascript:

 

 

(function () {

    var demo = document.getElementById('demo2');

    console.log(demo.dataset.user);

})();

为DOM实例进行扩展

HTML:

 

<p id="demo3"></p>

javascript:

 

(function () {

    var demo = document.getElementById('demo3');

    demo.userData = 'demo';

    console.log(demo.userData);

})();

虽然有解决方案,但都不是理想的解决方案,每个方案都有自己的局限性:

1、只能保存字符串(或转化为字符串类型)的数据,同时曝露了数据,并且在HTML上挂载了无谓的属性,浏览器仍然会尝试解析这些属性。

2、同上。

3、典型的污染,虽然可以保存更强大的数据(Object/Function),但是患有代码洁癖的骚年肯定是不能忍的,更主要,如果挂载的数据中引用着这个Element,则会循环引用。

jQuery.data,则是为了解决这样的自定义数据挂载问题。

 

 

模型

一窥模型吧,jQuery.data在早期,为了兼容性做了很多的事情。同时,或许是因为jQuery.data最初的需求作者也觉得太过简单,所以实现的代码上让人觉得略显仓促,早期的数据仓库很是繁琐,在jQuery.2.x后,jQuery.data重写,同时终于把jQuery.data抽离出对象。

 

jQuery.data模型上,就是建立一个数据仓库,而每一个挂载该数据的对象,都有自己的钥匙,他和上面的代码理念并不同:

 

上面的方案是:

 

在需要挂载数据的对象上挂载数据,就好像你身上一直带着1000块钱,要用的时候直接从口袋里掏就可以了。

 

jQuery.data则是:

 

建立一个仓库,所有的数据都放在这个仓库里,然后给每个需要挂载数据的对象一把钥匙,读取数据的时候拿这个钥匙到仓库里去拿,就好像所有人都把钱存在银行里,你需要的时候则拿着银行卡通过密码去取钱。

 

图一张:

 

 

 

我们暂时先不讨论数据仓库的样子,首先我们要关注数据和Element关联的关键点——钥匙,这个钥匙颇具争议,后续的几种数据缓存方式都是在对这个钥匙进行大的变动,因为这个钥匙,不得不放在Element上——即使你把所有的钱都存在银行里了,但是你身上还是要有相应的钥匙,这不得不让那些代码洁癖的童鞋面对这个问题:Element注定要被污染——jQuery.data只是尝试了最小的污染。

 

jQuery在创建的时候,会生成一个属性——jQuery.expando:

 

1

expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( //D/g, "" );

jQuery.expando是当前页面中引用的jquery对象的身份标志(id),每个页面引用的jQuery.expando都是不重复且唯一的,所以这就是钥匙的关键:jQuery.expando生成的值作为钥匙,挂载在Element上,也就是为Element创建一个属性:这个属性的名称,就是jQuery.expando的值,这就是钥匙的关键。 虽然仍然要在Element上挂载自己的数据,但是jQuery尽可能做到了最小化影响用户的东西。

 

当然这里需要注意:通过为Element添加钥匙的时候,使用的是jQuery.expando的值作为添加的属性名,页面每个使用过jQuery.data的Element上都有jQuery.expando的值扩展的属性名,也就是说,每个使用过jQuery.data的Element都有这个扩展的属性,通过检索这个属性值来找到仓库里的数据——钥匙是这个属性值,而不是这个jQuery.expando扩展的属性名。

 

木图木真相:

 

 

 

jQuery.1.x(截至jQuery.1.11)中,内部数据和外部数据挂载在jQuery.cache不同的地方——内部数据挂载在jQuery.cache[钥匙]下,而用户数据则挂载在jQuery.cache[钥匙].data下,原因是因为内部数据如何是用户数据挂载在一起则会存在相互覆盖的情况,要把数据给隔离开。

 

 

jQuery.1.x中jQuery.data实现

这里的jQuery.1.x主要是指jQuery.1.11。

 

jQuery.acceptData() - 目标过滤

因为jQuery.1.x是兼容低版本浏览器的,所以需要处理大量的浏览器兼容性,在jQuery.1.x中设计的jQuery.data是基于给目标添加属性来实现的,所以这其中找属性找钥匙找仓库很是繁琐,再加上IE低版本各种雷区,简直丧心病狂了已经。找钥匙找仓库还好说,低版本IE的雷区一踩一个爆:所以jQuery单独写了一个jQuery.acceptData用于屏蔽雷区,特别针对下面的情况:

 

applet和embed:这两个标签都是加载外部资的,这两个标签在js里可以操作的权限简直就是缩卵了——根本行不通,所以jQuery直接给干掉了,直接让他们不能放标签。

flash:早期的jQuery将所有的Object标签纳入雷区,后来发现IE下的flash还是可以自定义属性的,于是针对IE的flash还是网开一面,放过了IE的flash,IE下加载flash的时候,需要对object指定一个叫做classId的属性,它的值为:clsid:D27CDB6E-AE6D-11CF-96B8-444553540000。

jQuery.acceptData配合jQuery.noData做的过滤:

 

 

internalData() - 挂载/读取数据

挂载和读取数据方法是同一个(下面有分步分析):首先拿到钥匙,也就是jQuery.expando扩展的属性,然后根据钥匙获取仓库,因为内部数据和用户数据都是挂载在jQuery.cache下的,所以在jQuery.cache下开辟了jQuery.cache[钥匙].data作为用户数据存放的空间,而jQuery.cache[钥匙]则存放jQuery的内部数据,将数据挂上之后,返回的结果是这个数据挂载的空间/位置,通过这个返回值可以访问到这个Element所有挂载的数据。

 

太长看起来恶心?来,我们一点点分析:

 

1、首先,检测是否可以存放数据,可以的话初始化操作,针对变量id要注意,这里的一直在找上面我们所说的挂载在Element上那个存放钥匙的属性,也就是jQuery.expando的值

 

+ View Code

2、如果没有钥匙(id),则为目标添加上钥匙,代码如下:

 

 

//没有ID,则赋上ID

if (!id) {

    if (isNode) {

       /*

        DOM需要有一个全新的全局id

        为DOM建立一个jQuery的全局id

        低版本代码:elem[ internalKey ] = id = ++jQuery.uuid;

        这个deletedIds暂时忽略

        id = elem[internalKey] = deletedIds.pop() || jQuery.guid++;

        */

    } else {

        //而对象不需要

        id = internalKey;

    }

}

2、根据钥匙,尝试从cache中读仓库的位置,如果仓库中还没有这个目标的存放空间,则开辟一个,这里特别针对了JSON做了处理:当调用JSON.stringify序列化对象的时候,会调用这个对象的toJSON方法,为了保证jQuery.data里面数据的安全,所以直接重写toJSON为一个空方法(jQuery.noop),这样就不会曝露jQuery.data里面的数据。另外一种说法是针对HTML5处理的dataattr()(下面有讲)使用JSON.parse转换Object对象,而这个JSON可能是JSON2.js引入的:JSON2.js会为一系列原生类型添加toJSON方法,导致for in循环判定是否为空对象的时候无法正确判定——所以jQuery搞了个jQuery.noop来处理这里。

 

 

//从cache中没有读取到

if (!cache[id]) {

    //创建一个新的cache对象,这个toJson是个空方法

    cache[id] = isNode ? {} : { toJSON: jQuery.noop };

    /*

        对于javascript对象,设置方法toJSON为空函数,

        以避免在执行JSON.stringify()时暴露缓存数据。

        如果一个对象定义了方法toJSON()

        JSON.stringify()在序列化该对象时会调用这个方法来生成该对象的JSON元素

    */

}

3、如果是Function/Object,则直接调用jQuery.extend挂数据,把$(Element).data({'name':'linkFly'})这种调用方式的数据挂到jQuery.cache

 

/*

  先把Object/Function的类型的数据挂上。调用方式 :

  $(Element).data({'name':'linkFly'});

  这里的判定没有调用jQuery.tyPE()...当然在于作者的心态了...

*/

 

if (typeof name === "object" || typeof name === "function") {

    if (pvt) {//如果是内部数据

        //挂到cache上

        cache[id] = jQuery.extend(cache[id], name);

    } else {

        //如果是自定义数据,则挂到data上

        cache[id].data = jQuery.extend(cache[id].data, name);

    }

}

4、还有其他数据类型(String之类的)没有挂载上,这里把$(Element).data('name','value')的数据挂载上,最后要把这个data作为方法的返回值,这个返回值非常重要,从而实现也可以读取数据的功能。

 

+ View Code

代码上非常严谨,每一步都尽可能写在最恰当的地方,这里的方法会在jQuery最外层的API中和dataAttr()(下面会讲)方法一起配合来实现挂载/读取数据。

internalRemoveData() - 移除数据

数据移除方法移除数据方便比较简单,但是当仓库中没有相应Element存储的数据的时候,会直接从仓库中删除这个存储空间(下面有分步分析):

 

 

1、和internalData()一样,拿钥匙。

 

//移除一个data到jQuery缓存中

if (!jQuery.acceptData(elem)) {

    return;

}

 

var thisCache, i,

    isNode = elem.nodeType,

    cache = isNode ? jQuery.cache : elem,

        //根据jQuery标识拿钥匙

    id = isNode ? elem[jQuery.expando] : jQuery.expando;

//如果找不到缓存,不再继续

if (!cache[id]) {

    return;

}

2、找到仓库存储数据的位置,然后删除数据,这里充分的考虑了数据命名和Object参数的情况。

 

 e

3、如果数据全部删除了,那么仓库存储数据的空间也要被删除,所以接下来针对这些情况进行了处理

 

 

//如果不是jQuery内部使用

if (!pvt) {

    delete cache[id].data;// 连data也删除

    //检测还有没有数据,还有数据则继续

    if (!iSEMptyDataObject(cache[id])) {

        return;

    }

}

4、因为jQuery.data和jQuery.event事件系统直接挂钩,所以这里特别针对事件系统挂载的数据进行了删除处理,jQuery.cleanData方法涉及jQuery.event,所以暂不解读了。

 

//如果是Element,则破坏缓存

if (isNode) {

    //和jQuery.event挂钩,不分析了...

    jQuery.cleanData([elem], true);

} else if (support.deleteExpando || cache != cache.window) {

    //不为window的情况下,或者可以浏览器检测可以删除window.属性,再次尝试删除

    delete cache[id];

} else {

    //否则,直接粗暴的null

    cache[id] = null;

}

dataAttr()和jQuery.fn.data() - 针对HTML5的dataset和曝露API

dataAttr()是特别针对HTML5的dataset进行处理的方法,用处是读取Element上HTML5的data-*属性转换到jQuery.data中,是针对HTML5的兼容,典型的老夫就是要宠死你的方法:

 

 

jQuery.data实现很简单......个屁啊,妈蛋啊看起来就是调用internalData()实现,实际上jQuery.fn.data更加的健壮,同时将各种内层的方法都联接的惟妙惟肖,当然这也意味着性能更逊色一点,

 

1、针对获取全部数据做处理,同时在内部标识上parsedAttrs,表示这个Element已经被转换过HTML5属性了:

 

 

if (key === undefined) {

    //获取

    if (this.length) {

        data = jQuery.data(elem);

        //如果没有标志parsedAttrs的数据,则表示没有进行过HTML5的属性转换

        if (elem.nodeType === 1 && !jQuery._data(elem, "parsedAttrs")) {

            i = attrs.length;

            while (i--) {

                //那么转换HTML5的属性

                if (attrs[i]) {

                    name = attrs[i].name;

                    if (name.indexOf("data-") === 0) {

                        name = jQuery.camelCase(name.slice(5));

                        //配合dataAttr进行转换

                        dataAttr(elem, name, data[name]);

                    }

                }

            }

            //放上属性parsedAttrs,表示HTML5转换完毕

            jQuery._data(elem, "parsedAttrs", true);

        }

    }

 

    return data;

}

2、如果不是读取全部数据,则情况要么是挂载数据,要么是读取数据,但在最后的一段代码比较不错,是internalData()和dataAttr()的配合使用针对HTML5 dataset的兼容:

 

+ View Code

这里重点照顾最后一句,它实现了:

 

读取数据:$(Element).data('demo');

如果读取不到,读取HTML5的dataset数据并挂载到jQuery.cache中。

如果到了这里,那么调用方式会是:$(Elment).data('name'),这时候的处理方法就是:

 

jQuery.data底层是internalData(),当第三个参数为空的时候,则是读取数据

internalData()如果读取不到数据,则调用dataAttr(),而dataAttr第三个参数为undefined的时候,则会读取HTML5的dataset,然后再调用jQuery.data()(注意不是jQuery.fn.data)再挂一次数据。

好的各位爷,至此jQuery.1.x代码已经读完了,要不您老喝点茶看看窗外放松一下消化一下上面的代码?:

jQuery.expando是钥匙的关键,将jQuery.expando的值挂在Element上,就好像在你身上挂了一张银行卡,而银行卡的密码,则是jQuery.guid(累加不重复)。

通过钥匙找到仓库,进行操作。

internalData()的思路很值得借鉴,在挂数据的时候同时取数据,尤其在jQuery.cache这个相对比较复杂的环境里,如何更高效的取数据本身就是一件值得思考的事情。

internalRemoveData()实现了深度删除数据,尽可能让数据仿佛从未存在过,并且尝试了多种删除。

dataAttr()是针对HTML5特别的兼容处理。

internalData()方法非常的严谨,但是它仍然只是为了挂载数据和移除数据而生,非常纯粹而简单的工作着,真正让jQuery健壮的是jQuery.fn.data。

jQuery.2.x中jQuery.data实现

这里的jQuery.2.x主要是指jQuery.2.1。

 

在jQuery.2.x中,jQuery.data终于决定被好好深造一下了,过去1.x的代码说多了都是泪,jQuery.2.x没有了兼容性的后顾之忧,改写后的代码读起来简直不要太舒适啊。

 

在jQuery.2.x中,为数据缓存建立了Data对象,一个Data对象表示一个数据仓库——用户数据和内部数据各自使用不同的Data对象,这样就不需要在仓库里翻来翻去的查找数据存储的位置了(jQuery.cache[钥匙]和jQuery.cache[钥匙].data),思路上,仍然和jQuery.1.x一致,采用扩展属性的方式实现,关键点在Data.PRorotype.key()上。

 

Data对象 - 数据仓库

Data对象经过封装以后衍生了这些API:

 

key:专门用来获取和放置Element的钥匙。

set/get:放置和获取数据

access:通用API,根据参数既然可以放置也可以获取数据

remove:移除数据

hasData:检测是否有数据

discard:丢弃掉这个Element的存储空间

 

 

其他的实现都比较简单,我们需要关注钥匙这里,也就是Data.prototype.key()。

 

Data.prototype.key() - 钥匙

+ View Code

因为用户数据和jQuery内部数据通过Data分离,所以set/get在拿到钥匙之后都比较简单。

 

access() - 通用接口

在创建Data对象的时候,顺便为jQuery创建了静态方法——jQuery.access:通用的底层方法,既能设置也能读取,它应用在jQuery很多API中,例如:Text()、html()等。

 

+ View Code

jQuery.fn.data() - 曝露API

相比jQuery.1.x代码更加的细腻了许多,这里配合着上面定义的access()使用,为每一个循环的jQuery项设置和读取数据,阅读起来比较轻松。

 

+ View Code

各位看官到了这里可以继续小憩一下,后面我们再来谈谈关于这个jQuery.data更多有意思的事情...总结一下,jQuery.2.x的缓存设计理念清晰,最主要的就是封装成了Data对象以后将用户数据和jQuery内部使用的数据隔离开,这是最大的改进。移动端的Zepto里的Zepto.data是jQuery.data.2.x的浓缩版。

其他实现

这些实现都是在司徒正美的《javascript框架设计》 - "数据缓存系统"一章里读到的,有必要宣传和感谢一下这本书,了解了很多代码的由来促进了理解。

 

这些实现其实都是针对钥匙怎么交给Element这个问题上进行的探索

 

valueOf()重写

在jQuery.2.x最初设计的jQuery.data中,作者也在为Element挂载这个expando属性作为钥匙而头疼,于是给出了另外一种钥匙的挂载方法——重写valueOf()。 Waldron

 

在为Element挂载钥匙的时候,不再给这个Element声明属性,而是通过重写Element的valueOf方法实现。

 

虽然我翻了jQuery.2.0.0 - jQuery.2.1.1都没有找到这种做法,但觉得还是有必要提一下:function Data() {

        this.cache = {};

    };

    Data.uid = 1;

    Data.prototype = {

        locker: function (owner) {

            var ovalueOf,

                unlock = owner.valueOf(Data);

            /*

            owner为元素节点、文档对象、window

            传递Data类,如果返回object说明没有被重写,返回string则表示已被重写

            整个过程被jQuery称之为开锁,通过valueOf得到钥匙,进入仓库

            */

            if (typeof unlock !== 'string') {

                //通过闭包保存,也意味着内存消耗更大

                unlock = jQuery.expando + Data.uid++;

                //缓存原valueOf方法

                ovalueOf = owner.valueOf;

                Object.defineProperty(owner, 'valueOf', {

                    value: function (pick) {

                        //传入Data

                        if (pick === Data)

                            return unlock; //返回钥匙

                        return ovalueOf.apply(owner); //返回原valueOf方法

                    }

                });

            }

            if (!this.cache[unlock])

                this.cache[unlock] = {};

            return unlock;

        },

        get: function (owner, key) {

            var cache = this.cache[this.locker(owner)];

            return key === undefined ? cache : cache[key];

        },

        set: function (owner, key, value) {

            //略

        }

        /*其他方法略*/

    };

思路上很是新颖——因为在js中几乎所有的js数据类型(null,undefined除外)都拥有valueOf/toString方法,所以直接重写Element的valueOf,在传入Data对象的时候,返回钥匙,否则返回原valueOf方法——优点是钥匙隐性挂到了Element上,保证了Element的干净和无需再考虑挂属性兼不兼容等问题了,而缺点就是采用闭包,所以内存消耗更大,或许jQuery也觉得这种做法的内存消耗不能忍,所以仍未采用——相比较放置钥匙到Element的方式,还是后者更加的纯粹和稳定。

 

Array.prototype.indexOf()

Array.prototype.indexOf()是ECMAScript 5(低版本浏览器可以使用代码模拟)定义的方法——可以从一组Array中检索某项是否存在?存在返回该项索引:不存在则返回-1。听起来很相似?没错,它就是String.prototype.indexOf()的数组版。

 

正是因为提供了针对数组项的查找,所以可以采用新的思路:

 

1、将使用data()方法挂载数据的Element通过闭包缓存到一个数组中

2、当下次需要检索和这个Element关联的数据的时候,只需要通过Array.ptototype.indexOf在闭包中查找到这个数组即可,而闭包中这个数组查找到的索引,就是钥匙。

代码如下:

 

(function () {

    var caches = [],

        add = function (owner) {

            /*

 

            //拆开来是这样子的

            var length = caches.push(owner);//返回Array的length

            return caches[length - 1] = {};//新建对象并返回

            */

            return caches(caches.push(owner) - 1) = {};

        },

    addData = function (owner, name, data) {

        var index = caches.indexOf(owner), //查找索引,索引即是钥匙

        //获取仓库

            cache = index === -1 ? add(owner) : caches[index];

        //针对仓库放数据即可

    }

    //其他代码略

})();

这样就不需要在Element上挂载自定义的属性(钥匙)了——然而因为每个使用过data()的Element都会在缓存下来,那么内存的消耗必不可免,相比上一种重写valueOf重写消耗更加的不能直视,这是一个有趣但并不推荐的解决方案。

觉得可用,就经常来吧! 脚本宝典 欢迎评论哦! js脚本,巧夺天工,精雕玉琢。小宝典献丑了!

脚本宝典总结

以上是脚本宝典为你收集整理的javascript代码实例教程-jQuery源码解读 - 数据缓存系统:jQuery.data全部内容,希望文章能够帮你解决javascript代码实例教程-jQuery源码解读 - 数据缓存系统:jQuery.data所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。