惟有于技术中才能明得真相

jQuery源代码阅读(四):data方法

jQuery.data是一种给一个对象绑定数据的一种通用机制。为什么要这样复杂呢,如果我要给object绑定数据value,并以key作为查找关键字,不是将value赋给object的key属性(即object[key] = value就可以了吗?但是这样有两个问题:第一个是名字冲突问题,如果刚object也存在名字为key的属性,那么就会覆盖它已经存在的属性。但这个问题并不大,通常我们可以选择一个特别的名字,以至于它不会和已有属性名字冲突。最重要的还是第二问题,即它可能导致循环引用。如果value也引用了object,那么使用object[key] = value就会导致object引用value,循环引用会在某些浏览器(主要是IE)下会引起内存泄露。循环引用通常发生在事件处理器中,如果当我们需要一个单独的对象来处理元素的事件,这个对象就需要引用元素,同样为了在事件处理器中访问对象里数据,也需要在元素中引用这个对象,这样就形成了循环引用。那么jQuery使用另外一种方式来解决这个问题。在看它的源代码之前,我觉得有必要先看下其外部接口。

data函数接收两个参数,即data(elem, name, value),像很多其它jQuery函数一样,它既用作读取也用作设置,当value为null时,它返回elem对象中名字为name的数据对象,当value不为null,它设置elem对象中名字为name的数据对象为value,并返回该值。另外一个相关函数是removeData(elem, name)用于删除elem对象中名字为name的数据对象。这些函数的第一个参数通常为一个DOMElement,就像它的参数名字所暗示的那样,但它也可以为任何普通对象。看它的源代码:

1269 var expando = "jQuery" + now(), uuid = 0, windowData = {};
1270
1271 jQuery.extend({
1272     cache: {},
1273
1274     data: function( elem, name, data ) {
1275         elem = elem == window ?
1276             windowData :
1277             elem;
1278
1279         var id = elem[ expando ];
1280
1281         // Compute a unique ID for the element
1282         if ( !id )
1283             id = elem[ expando ] = ++uuid;
1284
1285         // Only generate the data cache if we're
1286         // trying to access or manipulate it
1287         if ( name && !jQuery.cache[ id ] )
1288             jQuery.cache[ id ] = {};
1289
1290         // Prevent overriding the named cache with undefined values
1291         if ( data !== undefined )
1292             jQuery.cache[ id ][ name ] = data;
1293
1294         // Return the named cache data, or the ID for the element
1295         return name ?
1296             jQuery.cache[ id ][ name ] :
1297             id;
1298     },


首先需要明白的一点,所有的数据对象都存于jQuery.cach(1272行)中,这个对象相当于一个数组。每个元素的所有数据对象都存于jQuery.cach中的一个条目,为了找到这个条目,元素需要存储它的数据元素在jQuery.cach对象中的索引,这个索引放在元素的对象的expando(1296行)属性中,expando是个变量,它的具体值由"jQuery"拼接当前的时间戳而成,类似于"jQuery1258874546757",虽然在不同的页面中它的值可能相同,但在同一个页面中它的值是确定的。由于每个元素不直接引用value对象,它只添加了一个整数属性值,因而不会造成循环引用。代码1297行是为了得到元素所有数据对象在jQuery.cache中的索引,如果没有的话,则通过使uuid加1来创建一个的索引值(1282-1283行),uuid是个全局变量,用来创建唯一的递增数字序列。第1287-1288行如果在jQuery.cache中不存在相应的数据条目,则创建它。最后如果data参数不为空,则将它存储到jQuery.cache中,最后返回数据值(第1295-1297行)。看源代码的实现,可以看到其实连name参数也是可以为空的,如果为空则会该元素返回一个唯一的uuid值,但这种方法调用只在内部使用。

让我们来举个例子,假设调用jQuery.data(elem1, "name1", "value1"),并且这是第一次调用,因此jQuery.cache是一个空对象。调用这个方法之后jQuery.cache则会包含一个元素,这个元素又包含name1属性,其对应值为"value1",即jQuery.cache = { "1" : { name1: "value1" }},elem1则会添加一个类似jQuery1258874546757的属性,对应值为1。如果接着调用jQuery.data(elem1, "name2", "value2"),则jQuery.cache变成 { "1": { name1: "value1", name2: "value2" }}。再调用jQuery.data(elem2, "name3", "value3"),这次是在一个新的元素上调用,会在jQuery.cache上添加一个新的元素,这是jQuery.cache变成为{ "1": ..., "2": { name3: "value3"}},同时elem2的jQuery1258874546757属性值为2。

明白了这,jQuery.removeData函数就不难理解了,这里就不作讲解了。另外jQuery.fn也带有data函数,不用传递elem参数,因为它隐含的就是对jQuery对象的每个元素调用jQuery.data函数,其源代码如下:

1367 jQuery.fn.extend({
1368     data: function( key, value ){
1369         var parts = key.split(".");
1370         parts[1] = parts[1] ? "." + parts[1] : "";
1371
1372         if ( value === undefined ) {
1373             var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
1374
1375             if ( data === undefined && this.length )
1376                 data = jQuery.data( this[0], key );
1377
1378             return data === undefined && parts[1] ?
1379                 this.data( parts[0] ) :
1380                 data;
1381         } else
1382             return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
1383                 jQuery.data( this, key, value );
1384             });
1385     },


需要注意的是其内部触发了事件调用,如果value空,则会触发getData事件,如果value不为空,则会触发setData事件,其它的也没什么可说的。

0 评论: