数据缓存,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 }