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.cache不同的地方——内部数据挂载在jQuery.cache[钥匙]下,而用户数据则挂载在jQuery.cache[钥匙].data下,原因是因为内部数据如何是用户数据挂载在一起则会存在相互覆盖的情况,要把数据给隔离开。这里的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) {//如果是内部数据 //挂到
新闻热点
疑难解答