【原创】jQuery1.8.2源码解析之jQuery.data

数据缓存,jQuery现在支持两种:

1. dom元素,数据存储在jQuery.cache中。

2.普通js对象,数据存储在该对象中。

 

 以下是源代码:

  1 var rbrace = /^(?:\{.*\}|\[.*\])$/,
  2     rmultiDash = /([A-Z])/g;
  3 
  4 // 首先是对jQuery对象自身的扩展
  5 jQuery.extend({
  6     // 即jQuery.cache,负责存储dom元素的缓存数据
  7     cache: {},
  8 
  9     // removeData时,缓存的数据被清除,返回的当时对应的id,以便再利用
 10     deletedIds: [], 
 11 
 12     // Please use with caution
 13     // 将数据存储到jQuery.cache中时,需要唯一id,用它来维护
 14     uuid: 0,
 15 
 16     // Unique for each copy of jQuery on the page
 17     // Non-digits removed to match rinlinejQuery
 18     // 内部key(随即生成),之后会作为key添加到dom的属性集中,而key对应的value则是该dom对应的缓存对象
 19     expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
 20 
 21     // The following elements throw uncatchable exceptions if you
 22     // attempt to add expando properties to them.
 23     // 不能添加expando属性的dom
 24     // classid为'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'的object是可以的,比较特殊吧
 25     noData: {
 26         "embed": true,
 27         // Ban all objects except for Flash (which handle expandos)
 28         "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
 29         "applet": true
 30     },
 31 
 32     hasData: function( elem ) {
 33         elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
 34         return !!elem && !isEmptyDataObject( elem );
 35     },
 36 
 37     data: function( elem, name, data, pvt /* Internal Use Only */ ) {
 38         if ( !jQuery.acceptData( elem ) ) {
 39             return;
 40         }
 41 
 42         var thisCache, ret,
 43             internalKey = jQuery.expando,
 44             getByName = typeof name === "string",
 45 
 46             // We have to handle DOM nodes and JS objects differently because IE6-7
 47             // can't GC object references properly across the DOM-JS boundary
 48             // 也就是说dom元素和普通js对象要进行不同的处理
 49             // 原因好像是是垃圾回收不能正确处理添加到dom元素的引用
 50             isNode = elem.nodeType,
 51 
 52             // Only DOM nodes need the global jQuery cache; JS object data is
 53             // attached directly to the object so GC can occur automatically
 54             // dom元素我们借用全局jQuery.cache来存储数据
 55             // 普通的js对象则直接将数据存储到对象中,垃圾回收可以自动处理
 56             cache = isNode ? jQuery.cache : elem,
 57 
 58             // Only defining an ID for JS objects if its cache already exists allows
 59             // the code to shortcut on the same path as a DOM node with no cache
 60             // 1. 如果是dom元素,返回dom元素expando对应的id(值可能为undefined)
 61             // 2. 如果是普通js对象,分两种情况:
 62             //    2.1 如果js对象存在expando对应的值,即代表有缓存数据,则立即返回expando作为id
 63             //    2.2 如果没有对应值,则代表没有缓存数据,此时返回undefined
 64             // 也就是说如果id不为空,那么肯定是有存储数据过的
 65             id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
 66 
 67         // Avoid doing any more work than we need to when trying to get data on an
 68         // object that has no data at all
 69         // 如果id不存在(表示不存在缓存)
 70         // 或者id存在,但是缓存为空
 71         // 又或者此时数据是私有的(pvt为true,仅为内部使用,此时只操控到cache[id]这一层)
 72         // 又或者数据不是私有的,但是对应的数据(data)为空
 73         // 以上条件之一成立后,
 74         // 再加上,getByName && data === undefined(表示是取数据)这个条件,直接return就可以了,因为没有数据取
 75         // 如果getByName为false,那么将初始化缓存对象(也为后来可能的name为object或者function时,extend做准备)
 76         if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
 77             return;
 78         }
 79         // 如果id为空,表示dom元素或者普通js对象没有缓存
 80         if ( !id ) {
 81             // Only DOM nodes need a new unique ID for each element since their data
 82             // ends up in the global cache
 83             // dom元素需要唯一id,因为它的数据将存在全局的jQuery.cache中
 84             if ( isNode ) {
 85                 elem[ internalKey ] = id = jQuery.deletedIds.pop() || ++jQuery.uuid;
 86             } else {
 87             // 普通js对象的id都是expando
 88                 id = internalKey;
 89             }
 90         }
 91 
 92         // 如果缓存为空,则之前没有存储过数据,此时需要进行必要的初始化
 93         if ( !cache[ id ] ) {
 94             // 创建缓存对象(理解为一个存放键值对的集合)
 95             cache[ id ] = {};
 96 
 97             // Avoids exposing jQuery metadata on plain JS objects when the object
 98             // is serialized using JSON.stringify
 99             // 普通js对象需要在它的缓存对象中添加toJSON方法,其中jQuery.noop只是有个空函数,什么都不做
100             // 这里的目的是:在使用JSON.stringify(elem)序列化该js对象时,使它的缓存对象不参与序列化(空函数返回空)
101             // 而dom元素是无法使用JSON.stringify(dom)的,会报错Converting circular structure to JSON
102             if ( !isNode ) {
103                 cache[ id ].toJSON = jQuery.noop;
104             }
105         }
106 
107         // An object can be passed to jQuery.data instead of a key/value pair; this gets
108         // shallow copied over onto the existing cache
109         // 就是说在适用jQuery.data()缓存数据时,除了传递key/value键值对外,还可以传递一个对象,或者一个函数(返回一个对象)
110         // 这样的结果是:传递的对象将会被extend到缓存中去
111         if ( typeof name === "object" || typeof name === "function" ) {
112             if ( pvt ) {
113                 // 私有数据,这里我明白了大概pvt的用处
114                 // cache[id].data 对象是用来存储用户自定义数据
115                 // cache[id] 则存储的是系统内部数据,比如之前说的toJSON
116                 // pvt不为空,则处理用户自定义数据,定位到cache[id].data这一层
117                 // pvt为空,则处理系统内部数据,定位到cache[id]这一层
118                 cache[ id ] = jQuery.extend( cache[ id ], name );
119             } else {
120                 cache[ id ].data = jQuery.extend( cache[ id ].data, name );
121             }
122         }
123 
124         thisCache = cache[ id ];
125 
126         // jQuery data() is stored in a separate object inside the object's internal data
127         // cache in order to avoid key collisions between internal data and user-defined
128         // data.
129         // 就是说为了防止系统内部数据和用户自定义数据的key发生冲突,才将用户数据包在thisCache.data中,
130         // 系统内部数据就是thisCache中
131         if ( !pvt ) {
132             if ( !thisCache.data ) {
133                 thisCache.data = {};
134             }
135 
136             //此时thisCache指向真正的数据缓存(集合)
137             thisCache = thisCache.data;
138         }
139 
140         // 如果data不为undefined,则表示这是在设置数据(set),那么进行负值缓存操作
141         // jQuery.camelCase( name )将name驼峰化
142         if ( data !== undefined ) {
143             thisCache[ jQuery.camelCase( name ) ] = data;
144         }
145 
146         // Check for both converted-to-camel and non-converted data property names
147         // If a data property was specified
148         // 返回指定name的数据,包括取数据和设置数据,都会返回。
149         if ( getByName ) {
150 
151             // First Try to find as-is property data
152             // 首先尝试取数据
153             ret = thisCache[ name ];
154 
155             // Test for null|undefined property data
156             // 如果ret为null或者undefined,则尝试将name驼峰化再尝试取数据(因为有可能之前name就被驼峰化)
157             if ( ret == null ) {
158                 // Try to find the camelCased property
159                 ret = thisCache[ jQuery.camelCase( name ) ];
160             }
161         } else {
162             // 没有指定name,则返回整个缓存对象
163             ret = thisCache;
164         }
165         //返回数据
166         return ret;
167     },
168 
169     removeData: function( elem, name, pvt /* Internal Use Only */ ) {
170         if ( !jQuery.acceptData( elem ) ) {
171             return;
172         }
173 
174         var thisCache, i, l,
175 
176             isNode = elem.nodeType,
177 
178             // See jQuery.data for more information
179             cache = isNode ? jQuery.cache : elem,
180             id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
181 
182         // If there is already no cache entry for this object, there is no
183         // purpose in continuing
184         // 没有缓存,直接退出
185         if ( !cache[ id ] ) {
186             return;
187         }
188 
189         // 如果有传入name,那么删除指定name对应的数据
190         // 否则删除所有缓存,后面的代码有这一步处理
191         if ( name ) {
192             // 这里还是一样,通过内部pvt指定缓存层级,用户自定义数据层和系统内部数据层
193             thisCache = pvt ? cache[ id ] : cache[ id ].data;
194 
195             if ( thisCache ) {
196 
197                 // Support array or space separated string names for data keys
198                 // 支持单个的key
199                 // 数组,多个key,如:[key1, key2, key3, ...]
200                 // 字符串,多个key,用空格隔开,如:'key1 key2 key3 ...'
201 
202                 //不是数组的情况,最终转换为数组形式
203                 if ( !jQuery.isArray( name ) ) {
204 
205                     // try the string as a key before any manipulation
206                     // 首先直接查找
207                     if ( name in thisCache ) {
208                         name = [ name ];        //转换成数组形式,便于后面统一操作
209                     } else {
210 
211                         // split the camel cased version by spaces unless a key with the spaces exists
212                         name = jQuery.camelCase( name );
213                         // 驼峰化后再查找
214                         if ( name in thisCache ) {
215                             name = [ name ];
216                         // 用字符串转换为数组
217                         } else {
218                             name = name.split(" ");
219                         }
220                     }
221                 }
222                 // 统一用数组进行删除操作
223                 // 有一个疑问就是,如果数组元素没有被驼峰化,应该会出错?!
224                 for ( i = 0, l = name.length; i < l; i++ ) {
225                     // delete thisCache[ jQuery.camelCase(name[i]) ];
226                     delete thisCache[ name[i] ];
227                 }
228 
229                 // If there is no data left in the cache, we want to continue
230                 // and let the cache object itself get destroyed
231                 // 如果缓存不为空,则退出
232                 // 否则,需要进行下一步的清理工作,因为此时缓存为空了嘛
233                 if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
234                     return;
235                 }
236             }
237         }
238 
239         // See jQuery.data for more information
240         if ( !pvt ) {
241             // 去除data属性
242             delete cache[ id ].data;
243 
244             // Don't destroy the parent cache unless the internal data object
245             // had been the only thing left in it
246             // 当data处理过后需要检测cache[id],因为此时cache[id]可能处于空的状态(这里的所谓的空在isEmptyDataObject有说明)
247             if ( !isEmptyDataObject( cache[ id ] ) ) {
248                 return;
249             }
250         }
251 
252         // Destroy the cache
253         // 如果是dom元素,除了jQuery.cache清理完毕后,还要处理dom元素自身,因为绑定了一个id嘛
254         if ( isNode ) {
255             jQuery.cleanData( [ elem ], true );
256 
257         // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
258         } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
259             delete cache[ id ];
260 
261         // When all else fails, null
262         } else {
263             cache[ id ] = null;
264         }
265     },
266 
267     // For internal use only.
268     // 内部适用,这里设置pvt为true,返回内部数据,定位到cache[id]这一层
269     _data: function( elem, name, data ) {
270         return jQuery.data( elem, name, data, true );
271     },
272 
273     // A method for determining if a DOM node can handle the data expando
274     // 根据上面的jQuery.noData属性判断dom该元素是否可以添加expando属性(即dom是否允许添加数据)
275     // 其中,classid为'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'的object是可以的,比较特殊吧
276     acceptData: function( elem ) {
277         var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
278 
279         // nodes accept data unless otherwise specified; rejection can be conditional
280         return !noData || noData !== true && elem.getAttribute("classid") === noData;
281     }
282 });
283 
284 // 接下来是对jQuery对象($(selector))的扩展
285 jQuery.fn.extend({
286     data: function( key, value ) {
287         var parts, part, attr, name, l,
288             elem = this[0],
289             i = 0,
290             data = null;
291 
292         // Gets all values
293         // 如果为连key的值都未指定,那么返回的所有数据
294         // 如:$dom.data();
295         if ( key === undefined ) {
296             if ( this.length ) {
297                 // 先从jquery缓存中取出所有数据
298                 data = jQuery.data( elem );
299 
300                 // 对于元素节点而言,数据可以来自两个地方:
301                 // 1. jQuery.cache缓存中,之前手动存进去的,如:$dom.data('data1', value1);
302                 // 2. 来自html标签的以data-开头的属性,之后该属性的数据也会被存储到jQuery.cache缓存中,
303                 //    (属性名采用驼峰的形式)避免每次都要去html标签里去匹配并取值
304                 //    如:<div data-data-first="{a:1,b:2}" data-data-second="hello">hello world</div>
305                 //    当使用$dom.data()时,会获取到:
306                 // {
307                 //     dataFirst : {
308                 //         a : 1,
309                 //         b : 2
310                 //     }
311                 //     dataSecond : 'hello world'
312                 // }
313 
314                 // 通过缓存中的内部属性parsedAttrs,分析html标签属性所带的数据是否被解析过(即存到jQuery过缓存中)
315                 // 解析过了,那么这里就没必要再解析一遍了,上面一步就已经取到数据了
316                 if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
317                     attr = elem.attributes;
318                     // 遍历dom节点的属性列表
319                     for ( l = attr.length; i < l; i++ ) {
320                         name = attr[i].name;
321                         // 对于属性名以data-开头的属性进行取值存储操作
322                         if ( name.indexOf( "data-" ) === 0 ) {
323                             // 首先name去除data-,并将剩余的字符驼峰化
324                             name = jQuery.camelCase( name.substring(5) );
325 
326                             dataAttr( elem, name, data[ name ] );
327                         }
328                     }
329                     // 标记html标签上的数据已经解析过
330                     jQuery._data( elem, "parsedAttrs", true );
331                 }
332             }
333 
334             return data;
335         }
336 
337         // Sets multiple values
338         // 传递对象(键值对)作为data缓存,此时是对jQuery对象列表进行each操作
339         if ( typeof key === "object" ) {
340             return this.each(function() {
341                 jQuery.data( this, key );
342             });
343         }
344 
345         parts = key.split( ".", 2 );
346         parts[1] = parts[1] ? "." + parts[1] : "";
347         part = parts[1] + "!";
348 
349         return jQuery.access( this, function( value ) {
350 
351             if ( value === undefined ) {
352                 data = this.triggerHandler( "getData" + part, [ parts[0] ] );
353 
354                 // Try to fetch any internally stored data first
355                 if ( data === undefined && elem ) {
356                     // 首先从jQuery缓存中获取
357                     data = jQuery.data( elem, key );
358                     // 再从html标签里面获取(可见标签数据的优先级高)
359                     data = dataAttr( elem, key, data );
360                 }
361 
362                 return data === undefined && parts[1] ?
363                     this.data( parts[0] ) :
364                     data;
365             }
366 
367             parts[1] = value;
368             this.each(function() {
369                 var self = jQuery( this );
370 
371                 self.triggerHandler( "setData" + part, parts );
372                 jQuery.data( this, key, value );
373                 self.triggerHandler( "changeData" + part, parts );
374             });
375         }, null, value, arguments.length > 1, null, false );
376     },
377 
378     removeData: function( key ) {
379         return this.each(function() {
380             jQuery.removeData( this, key );
381         });
382     }
383 });
384 
385 function dataAttr( elem, key, data ) {
386     // If nothing was found internally, try to fetch any
387     // data from the HTML5 data-* attribute
388     // 如果data为空,且elem为元素节点,那么从标签的数据属性取数据(遵循html5)
389     if ( data === undefined && elem.nodeType === 1 ) {
390         // 将驼峰化转换成'-'连接的小写字符串
391         var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
392         // 获取dom上的对应属性
393         data = elem.getAttribute( name );
394         // 如果该属性存在,此时data为字符串,下面将进行根据数据类型进行数据的格式化
395         if ( typeof data === "string" ) {
396             try {
397                 // 布尔型
398                 data = data === "true" ? true :
399                 data === "false" ? false :
400                 // null
401                 data === "null" ? null :
402                 // Only convert to a number if it doesn't change the string
403                 // +data用来测试类型是否为数字
404                 +data + "" === data ? +data :
405                 // 对象和数组
406                 rbrace.test( data ) ? jQuery.parseJSON( data ) :
407                     data;
408             } catch( e ) {}
409 
410             // Make sure we set the data so it isn't changed later
411             // 将格式化的数据存在jQuery.cache缓存。
412             //(注意这里存的是jQuery.cache中,也就是说之前通过$dom.data()获取的对象,因为是引用,所以此时也是有值的)
413             jQuery.data( elem, key, data );
414 
415         } else {
416             // 如果该属性不存在,此时data为null,将其转换为undefined
417             data = undefined;
418         }
419     }
420     // 返回标签属性数据
421     return data;
422 }
423 
424 // checks a cache object for emptiness
425 // 内部使用,用于检测cache[id]这一层是否为空
426 // 其中,toJSON 不参与检测,也就是说只有它存在时,也算是空
427 // 其中,data 参与检测,如果data不为空,整个cache[id]则不为空
428 function isEmptyDataObject( obj ) {
429     var name;
430     for ( name in obj ) {
431 
432         // if the public data object is empty, the private is still empty
433         // 对于data属性,需要额外判断data里面是否有数据
434         // 如果没有,则data为空,那么跳过data,继续检测
435         // 否则将在下面的返回false,表示不为空
436         if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
437             continue;
438         }
439         // toJSON 这个方法可以认为是是内置的,可以忽略它。
440         // 如果不是toJSON 一律返回false,表示还有其他数据
441         if ( name !== "toJSON" ) {
442             return false;
443         }
444     }
445 
446     return true;
447 }
posted @ 2012-10-24 21:57  Lovesueee  阅读(3765)  评论(2编辑  收藏  举报