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,原文地址。
这篇文章主要分为以下知识
凡存在,皆真理——任何一样事物的存在必然有其存在的理由,于我们的角度来说,这叫需求。
一组数据,如何与DOM相关联一直是web前端的痛处,因为浏览器的兼容性等因素。最初的jQuery事件系统照搬Dean Edwards的addEvent.js:将回调挂载在EventTarget上,这样下来,循环引用是不可忽视的问题,它把事件的回调都放在相应的EventTarget上,当回调中再引用EventTarget的时候,会造成循环引用。于是缔造了jQuery.data,在jQuery.event中通过jQuery.data挂载回调函数,这样解决了回调函数的循环引用,随时时间的推移,jQuery.data应用越来越广,例如后来的jQuery.queue。
首先我们要搞清楚jQuery.data解决的需求,有一组和DOM相关/描述Element的数据,如何存放和挂载呢?可能有人是这样的:
HTML:
<div id="demo" userData="linkFly"></div>
(function () {            var demo = document.getElementById('demo');            console.log(demo.getAttribute('userData'));})();HTML:
<div id="demo2" data-user="linkFly"></div>
        (function () {            var demo = document.getElementById('demo2');            console.log(demo.dataset.user);        })();HTML:
<div id="demo3"></div>
javascript:
        (function () {            var demo = document.getElementById('demo3');            demo.userData = 'demo';            console.log(demo.userData);        })();虽然有解决方案,但都不是理想的解决方案,每个方案都有自己的局限性:一窥模型吧,jQuery.data在早期,为了兼容性做了很多的事情。同时,或许是因为jQuery.data最初的需求作者也觉得太过简单,所以实现的代码上让人觉得略显仓促,早期的数据仓库很是繁琐,在jQuery.2.x后,jQuery.data重写,同时终于把jQuery.data抽离出对象。
jQuery.data模型上,就是建立一个数据仓库,而每一个挂载该数据的对象,都有自己的钥匙,他和上面的代码理念并不同:
上面的方案是:
在需要挂载数据的对象上挂载数据,就好像你身上一直带着1000块钱,要用的时候直接从口袋里掏就可以了。
jQuery.data则是:
建立一个仓库,所有的数据都放在这个仓库里,然后给每个需要挂载数据的对象一把钥匙,读取数据的时候拿这个钥匙到仓库里去拿,就好像所有人都把钱存在银行里,你需要的时候则拿着银行卡通过密码去取钱。
图一张:

我们暂时先不讨论数据仓库的样子,首先我们要关注数据和Element关联的关键点——钥匙,这个钥匙颇具争议,后续的几种数据缓存方式都是在对这个钥匙进行大的变动,因为这个钥匙,不得不放在Element上——即使你把所有的钱都存在银行里了,但是你身上还是要有相应的钥匙,这不得不让那些代码洁癖的童鞋面对这个问题:Element注定要被污染——jQuery.data只是尝试了最小的污染。
jQuery在创建的时候,会生成一个属性——jQuery.expando:
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.1.x是兼容低版本浏览器的,所以需要处理大量的浏览器兼容性,在jQuery.1.x中设计的jQuery.data是基于给目标添加属性来实现的,所以这其中找属性找钥匙找仓库很是繁琐,再加上IE低版本各种雷区,简直丧心病狂了已经。找钥匙找仓库还好说,低版本IE的雷区一踩一个爆:所以jQuery单独写了一个jQuery.acceptData用于屏蔽雷区,特别针对下面的情况:
jQuery.acceptData配合jQuery.noData做的过滤:
    jQuery.extend({        //jQuery.cache对象,仓库        cache: {},        noData: {            //有可能权限不够,所以过滤            "applet ": true,            "embed ": true,            //ie的flash可以通过            "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"        }        //其余API代码略    });    jQuery.acceptData = function (elem) {        //确定一个对象是否允许设置Data         var noData = jQuery.noData[(elem.nodeName + " ").toLowerCase()],nodeType = +elem.nodeType || 1;        //进行过滤        return nodeType !== 1 && nodeType !== 9 ?false :        //如果是jQuery.noData里面限定的节点的话,则返回false        //如果节点是object,则判定是否是IE flash的classid!noData || noData !== true && elem.getAttribute("classid") === noData;    };挂载和读取数据方法是同一个(下面有分步分析):首先拿到钥匙,也就是jQuery.expando扩展的属性,然后根据钥匙获取仓库,因为内部数据和用户数据都是挂载在jQuery.cache下的,所以在jQuery.cache下开辟了jQuery.cache[钥匙].data作为用户数据存放的空间,而jQuery.cache[钥匙]则存放jQuery的内部数据,将数据挂上之后,返回的结果是这个数据挂载的空间/位置,通过这个返回值可以访问到这个Element所有挂载的数据。
 function internalData(elem, name, data, pvt /* Internal Use Only */) {        //pvt:标识是否是内部数据        //判定对象是否可以存数据        if (!jQuery.acceptData(elem)) {            return;        }        var ret, thisCache,              //来自jQuery随机数(每一个页面上唯一且不变的)    internalKey = jQuery.expando,            /*        如果是DOM元素,        为了避免javascript和DOM元素之间循环引用导致的浏览器(IE6/7)垃圾回收机制不起作用,        要把数据存储在全局缓存对象jQuery.cache中            */    isNode = elem.nodeType,    //只有DOM节点才需要全局缓存,js对象是直接连接到对象的            //如果是DOM,则cache连接到jQuery.cache    cache = isNode ? jQuery.cache : elem,    //如果是DOM,则获取钥匙,如果是第一次读取,则读取不到钥匙    id = isNode ? elem[internalKey] : elem[internalKey] && internalKey;        //检测合法性,避免做更多的工作,pvt标识是否是内部数据        if ((!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string") {            return;        }        //没有拿到钥匙,则赋上ID        if (!id) {            if (isNode) {                /*                   DOM需要有一个全新的全局id,                   为DOM建立一个jQuery的全局id                   低版本代码:elem[ internalKey ] = id = ++jQuery.uuid;                   这个deletedIds暂时忽略,当初jQuery准备重用uuid,后来被guid取代了                */                id = elem[internalKey] = deletedIds.pop() || jQuery.guid++;            } else {                //对象就不用这么麻烦了,直接挂钥匙就可以了                id = internalKey;            }        }        //从jQuery.cache中没有读取到,则开辟一个新的        if (!cache[id]) {            //创建一个新的cache对象,这个toJson是个空方法            cache[id] = isNode ? {} : { toJSON: jQuery.noop };            /*              对于javascript对象,设置方法toJSON为空函数,              以避免在执行JSON.stringify()时暴露缓存数据。              如果一个对象定义了方法toJSON()              JSON.stringify()在序列化该对象时会调用这个方法来生成该对象的JSON元素            */        }            /*              先把Object/Function的类型的数据挂上。调用方式 :              $(Element).data({'name':'linkFly'});              这里的判定没有调用jQuery.type()...当然在于作者的心态了...            */        if (typeof name === "object" || typeof name === "function") {            if (pvt) {//如果是内部数据                //挂到
新闻热点
疑难解答