深入理解jQuery.data() 的实现方式
jQuery.data()的作用是为普通对象或DOMElement附加(及获取)数据。
下面将分三个部分分析其实现方式:
1.用name和value为对象附加数据;即传入三个参数,第一个参数为需要附加数据的对象,第二个参数为数据的名称,第三个参数为数据的值。当然,只是获取值的话,也可以不传入第三个参数。
2.用另一个对象为对象附加数据;即传入两个参数,第一个参数为需要附加的数据对象(我们称之为“obj”),第二个参数也是一个对象(我们称之为“another”);“another”中包含的键值对将会被复制到“obj”的数据缓存(我们称之为“cache”)中。
3.为DOMElement附加数据;DOMElement也是一种Object,但IE6、IE7对直接附加在DOMElement上的对象的垃圾回收存在问题;因此我们将这些数据存放在全局缓存(我们称之为“globalCache”)中,即“globalCache”包含了多个DOMElement的“cache”,并在DOMElement上添加一个属性,存放“cache”对应的uid。
用name和value为对象附加数据
使用jQuery.data()为普通对象附加数据时,其本质是将一个“cache”附加到了对象上,并使用了一个特殊的属性名称。
存放数据的“cache”也是一个object,我们为“obj”附加的数据实际上成为了“cache”的属性。而“cache”又是“obj”的一个属性,在jQuery1.6中,这个属性的名称是“jQuery16”加上一个随机数(如下面提到的“jQuery16018518865841457738”)。
我们可以用下面的代码来测试jQuery.data()的功能:
<scripttype="text/javascript"src="jqueryjs"></script> <script> obj={}; $data(obj,'name','value'); documentwrite("$data(obj,'name')="+$data(obj,'name')+'<br/>'); for(varkeyinobj){ documentwrite("obj"+key+'name='+obj[key]name); } </script>
显示结果为:
$.data(obj,'name')=value obj.jQuery16018518865841457738.name=value
在这段代码中,我们首先在“obj”上附加了一个属性(名称为“name”,值为“value”),然后通过$.data(obj,'name')来获取所附加的数据。为了深入了解其中的实现机制,我们有使用了一个循环来获取“obj”的属性,实际上是取出了在“obj”上附加的“cache”对象。
可以看到,jQuery.data()实际上为“obj”附加到了名为“jQuery16018518865841457738”(这个名称是随机的)的对象,也就是“cache”上。用jquery.data()方式为对象附加的属性实际上成为了这个“cache”的属性。
我们可以用下面的代码实现类似的功能:
$=function(){ varexpando="jQuery"+("6"+Mathrandom())replace(/\D/g,''); functiongetData(cache,name){ returncache[name]; } functionsetData(cache,name,value){ cache[name]=value; } functiongetCache(obj){ obj[expando]=obj[expando]||{}; returnobj[expando]; } return{ data:function(obj,name,value){ varcache=getCache(obj); if(value===undefined){ returngetData(cache,name); }else{ setData(cache,name,value); } } } }();
function中的第一行代码定义了“expando”,即"jQuery1.6"加上一个随机数(0.xxxx),并将其中非数字的部分去掉;这种格式将在jQuery的其他地方用到,这里不做探讨;只需要知道这是一个特殊的名称,并且可以用于标识不同的页面(比如不同iframe中的“expando”就会有所不同)。
接下来定义了获取数据的函数getData(),即从“cache”中获取一个属性;实际上也就是返回cache[name]。
然后是setData()函数,用于设置“cache”的属性;实际上也就是设置cache[name]的值。
之后是getCache(),获取“obj”上的“cache”,即obj[expando];如果obj[expando]为空,则进行初始化。
最后公开了data方法,先根据传入的“obj”,获取附加在“obj”上的“cache”;当传入两个参数时,调用getData()方法;当传入三个参数时,则调用setData()方法。
用另一个对象为对象附加数据
除了以提供name和value的方式进行赋值,我们还可以直接传入另一个对象(“another”)作为参数。这种情况下,“another”的属性名称和属性值将被视为多个键值对,从中提取的“name”和“value”都会被复制到目标对象的缓存中。
功能测试代码如下:
<scripttype="text/javascript"src="jqueryjs"></script> <script> obj={}; $data(obj,{name1:'value1',name2:'value2'}); documentwrite("$data(obj,'name1')="+$data(obj,'name1')+'<br/>'); documentwrite("$data(obj,'name2')="+$data(obj,'name2')+'<br/>'); for(varkeyinobj){ documentwrite("obj"+key+'name1='+obj[key]name1+'<br/>'); documentwrite("obj"+key+'name2='+obj[key]name2); } </script>
显示结果如下:
$.data(obj,'name1')=value1 $.data(obj,'name2')=value2 obj.jQuery1600233050178663064.name1=value1 obj.jQuery1600233050178663064.name2=value2
上面的测试代码中,我们先将一个带有两个键值对的“another”对象传入,然后分别用$.data(obj,'name1')和$.data(obj,'name2')获取附加的数据;同样,为了深入了解其中的机制,我们通过遍历“obj”的方式取出了隐藏的“cache”对象,并获得了“cache”对象的“name1”属性和“name2”属性的值。
可以看到,jQuery.data()实际上为“obj”附加了名为“obj.jQuery1600233050178663064”的对象,也就是“cache”上。用jquery.data()方式传入的键值对都被复制到了“cache”中。
我们可以用下面的代码实现类似的功能:
$=function(){ //Othercodes functionsetDataWithObject(cache,another){ for(varnameinanother){ cache[name]=another[name]; } } //Othercodes return{ data:function(obj,name,value){ varcache=getCache(obj); if(nameinstanceofObject){ setDataWithObject(cache,name) }elseif(value===undefined){ returngetData(cache,name); }else{ setData(cache,name,value); } } } }();
这段代码是在之前的代码的基础上进行修改的。首先增加了内部函数setDataWithObject(),这个函数的实现是遍历“another”的属性,并复制到“cache”中。
然后,在对外开放的data函数中,先判断传入的第二个参数的名称,如果这个参数是一个Object类型的实例,则调用setDataWithObject()方法。
为DOMElement附加数据
由于DOMElement也是一种Object,因此之前的方式也可以为DOMElement赋值;但考虑到IE6、IE7中垃圾回收的问题(不能有效回收DOMElement上附加的对象引用),jQuery采用了与普通对象有所不同的方式附加数据。
测试代码如下:
<scripttype="text/javascript"src="datajs"></script> <script> windowonload=function(){ div=documentgetElementById('div_test'); $data(div,'name','value'); documentwrite($data(div,'name')); } </script>
显示结果如下:
value
测试代码中,首先通过document.getElementById方法获取了一个DOMElement(当然,也可以用jQuery的选择器),然后在这个DOMElement上附加了一个属性,随后就从DOMElement上取出了附加的属性并输出。
因为考虑到IE6、IE7对DOMElement上的对象引用的垃圾回收存在问题,我们不会直接在DOMElement上附加对象;而是使用全局cache,并在DOMElement上附加一个uid。
实现方式如下:
$=function(){ varexpando="jQuery"+("6"+Mathrandom())replace(/\D/g,''); varglobalCache={}; varuuid=0; //Othercodes functiongetCache(obj){ if(objnodeType){ varid=obj[expando]=obj[expando]||++uuid; globalCache[id]=globalCache[id]||{}; returnglobalCache[id]; }else{ obj[expando]=obj[expando]||{}; returnobj[expando]; } } //Othercodes }();
这段代码与之前的代码相比,增加了globalCache和uuid,并修改了getCache()方法。
globalCache对象用于存放附加到DOMElement上的“cache”,可以视为“cache”的“容器”。uuid表示“cache”对应的唯一标识,是唯一且自增长的。uuid或被存放在DOMElement的“expando”属性中。
getCache()函数中增加了一个判断,即“obj”具有“nodeType”属性,就认为这是一个DOMElement;这种情况下,就先取出附加在“obj”上的id,即obj[expando];如果obj[expando]未定义,则先用++uuid对其进行初始化;取出id之后,就到globalCache中找到对应的“cache”,即globalCache[id],并返回。
到此为止,jQuery.data()函数的实现就介绍完了;但是,这里还有一个需要思考的问题:为什不都统一用“globalCache”存储,而要将“cache”直接附加到普通对象上?我认为这应该是一种性能优化的方式,毕竟少一个引用的层次,存取速度应该会略快一些。jQuery中这刻意优化的地方非常多,在许多原本可以统一处理的对方都进行了特殊处理。但这在一定程度上,也造成了阅读源码的障碍。当然这是作者(及其他代码贡献者)本身的编程哲学,这里就不加评论了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。