Skip to main content
 首页 » 编程设计

jQuery之data缓存对象源码分析

2022年07月16日48linjiqin
  1 // 匹配结尾是否有“{...}”或"[...]" 
  2     var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, 
  3         // 匹配大写字母 
  4         rmultiDash = /([A-Z])/g; 
  5  
  6     /** 
  7      * 内部用来设置/获取元素或对象的缓存方法 
  8      * 
  9      * @param elem DOM元素或者JS对象 
 10      * @param name 缓存的标识符key 
 11      * @param data 缓存数据 
 12      * @param {Boolean} pvt 当为true时表示是私有性的,jq内部使用的 
 13      */ 
 14  
 15     function internalData(elem, name, data, pvt /* Internal Use Only */ ) { 
 16         // 判断该对象能不能绑定数据 
 17         if (!jQuery.acceptData(elem)) { 
 18             return; 
 19         } 
 20  
 21         var thisCache, ret, 
 22             // expando是jQuery生成的随机ID 
 23             internalKey = jQuery.expando, 
 24             getByName = typeof name === 'string', 
 25             // 我们不得不分别处理DOM元素和js对象, 
 26             // 因为ie6/7的垃圾回收不能正确处理对DOM元素的对象引用 
 27             isNode = elem.nodeType, 
 28             // 只有DOM元素才需要全局jQuery.cache对象。 
 29             // js对象数据直接指向该对象,垃圾回收可以自动处理 
 30             cache = isNode ? jQuery.cache : elem, 
 31             // 1. 如果是dom元素,返回dom元素通过expando对应的id(值可能为undefined) 
 32             // 2. 如果是普通js对象,分两种情况: 
 33             //    2.1 如果js对象存在通过expando对应的值,即代表有缓存数据,则立即返回expando作为id 
 34             //    2.2 如果没有对应值,则代表没有缓存数据,此时返回undefined 
 35             // 也就是说如果id不为空,那么肯定是有存储数据过的 
 36             id = isNode ? elem[internalKey] : elem[internalKey] && internalKey; 
 37  
 38         // 当一个对象没有data的时候返回,避免多余工作 
 39         if ((!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined) { 
 40             return; 
 41         } 
 42  
 43         // 如果没有ID 
 44         if (!id) { 
 45             // 如果是DOM元素,给该节点绑定一个属性ID 
 46             if (isNode) { 
 47                 elem[internalKey] = id = core_deletedIds.pop() || jQuery.guid++; 
 48             } else { 
 49                 // 否则是对象则通过expando创建一个唯一ID 
 50                 id = internalKey; 
 51             } 
 52         } 
 53  
 54         // 如果cache对象没有指定id属性 
 55         if (!cache[id]) { 
 56             cache[id] = {}; 
 57  
 58             // 当为JS对象时,为了避免被JSON.stringify序列化 
 59             // 这里将toJSON方法设为空方法,这样就会返回空值 
 60             if (!isNode) { 
 61                 cache[id].toJSON = jQuery.noop; 
 62             } 
 63         } 
 64  
 65  
 66         // 如果name是对象或函数,当存在pvt将name浅复制给cache[id], 
 67         // 否则浅复制给cache[id].data 
 68         if (typeof name === 'object' || typeof name === 'function') { 
 69             if (pvt) { 
 70                 cache[id] = jQuery.extend(cache[id], name); 
 71             } else { 
 72                 cache[id].data = jQuery.extend(cache[id].data, name); 
 73             } 
 74         } 
 75  
 76         thisCache = cache[id]; 
 77  
 78         // 为了防止系统内部数据和用户自定义数据的key发生冲突,才将用户数据包在thisCache.data中, 
 79         // pvt的意思是保持私有性,非私有性时对外提供data属性对象 
 80         // 系统内部数据就是thisCache中 
 81         if (!pvt) { 
 82             if (!thisCache.data) { 
 83                 thisCache.data = {}; 
 84             } 
 85  
 86             // 只对外开放thisCache.data属性值 
 87             thisCache = thisCache.data; 
 88         } 
 89  
 90         // 如果data不为undefined,将data赋值给thisCache的通过驼峰式的name属性 
 91         if (data !== undefined) { 
 92             thisCache[jQuery.camelCase(name)] = data; 
 93         } 
 94  
 95         // 如果name是字符串 
 96         if (getByName) { 
 97             // 尝试获取thisCache的属性data 
 98             ret = thisCache[name]; 
 99  
100             // 如果ret为null或undefined,则尝试获取驼峰式的name属性data值 
101             if (ret == null) { 
102                 ret = thisCache[jQuery.camelCase(name)]; 
103             } 
104         } else { 
105             // 否则name为非字符串时,ret指向thisCache 
106             ret = thisCache; 
107         } 
108  
109         return ret; 
110     } 
111  
112     /** 
113      * 删除对应的缓存数据 
114      * 
115      * @param elem 
116      * @param name 
117      * @param pvt 
118      */ 
119  
120     function internalRemoveData(elem, name, pvt) { 
121         if (!jQuery.acceptData(elem)) { 
122             return; 
123         } 
124  
125         var i, l, thisCache, 
126             isNode = elem.nodeType, 
127             cache = isNode ? jQuery.cache : elem, 
128             id = isNode ? elem[jQuery.expando] : jQuery.expando; 
129  
130         // 如果没有缓存对象,返回 
131         if (!cache[id]) { 
132             return; 
133         } 
134  
135         if (name) { 
136             thisCache = pvt ? cache[id] : cache[id].data; 
137  
138             if (thisCache) { 
139                 // 支持单个的key 
140                 // 数组,多个key,如:[key1, key2, key3, ...] 
141                 // 字符串,多个key,用空格隔开,如:'key1 key2 key3 ...' 
142  
143                 // 如果name不是数组类型,将name转换为数组类型 
144                 if (!jQuery.isArray(name)) { 
145                     // 如果name是thisCache的一个属性key 
146                     if (name in thisCache) { 
147                         // 用数组保存 
148                         name = [name]; 
149                     } else { 
150                         // 将name驼峰化 
151                         name = jQuery.camelCase(name); 
152                         // 此时若name是thisCache的一个属性key 
153                         if (name in thisCache) { 
154                             // 同样转换成数组 
155                             name = [name]; 
156                         } else { 
157                             // 否则name是个多个空白分隔的字符串 
158                             name = name.split(' '); 
159                         } 
160                     } 
161                     // 如果是数组,将name数组各项驼峰化后追加到name数组里 
162                 } else { 
163                     name = name.concat(jQuery.map(name, jQuery.camelCase)); 
164                 } 
165  
166                 // 遍历删除name数组里的各项key属性 
167                 for (i = 0, l = name.length; i < l; i++) { 
168                     delete thisCache[name[i]]; 
169                 } 
170  
171                 // 如果pvt为true,检查thisCache是否为空的数据对象,如果不是直接退出函数 
172                 // 如果pvt为false,判断thisCache是否为空对象,如果不是也是退出 
173                 // 这里考虑到用户自定义或者其他私有受保护的属性 
174                 if (!(pvt ? isEmptyDataObject : jQuery.isEmptyObject)(thisCache)) { 
175                     return; 
176                 } 
177             } 
178         } 
179  
180         // 如果pvt为false,即非私有性 
181         // 删除data属性值 
182         if (!pvt) { 
183             delete cache[id].data; 
184  
185             // 同理,这时cache[id]还存在其他属性,退出 
186             if (!isEmptyDataObject(cache[id])) { 
187                 return; 
188             } 
189         } 
190  
191         // 如果是DOM元素,清除绑定在elem上的所有数据 
192         if (isNode) { 
193             jQuery.cleanData([elem], true); 
194         } else if (jQuery.support.deleteExpando || cache != cache.window) { 
195             // 如果支持删除绑定在对象上的expando属性或者cache非window对象 
196             // 只用delete就可以删除了 
197             delete cache[id]; 
198         } else { 
199             // 其他情况就将属性设为null来清空缓存 
200             cache[id] = null; 
201         } 
202     } 
203  
204     jQuery.extend({ 
205         // 当是DOM元素的时候,使用$.cache来缓存数据 
206         cache: {}, 
207         // 生成expando字符串 
208         expando: 'jQuery' + (core_version + Math.random()).replace(/\D/g, ''), 
209         // 以下情况不需要缓存 
210         noData: { 
211             'embed': true, 
212             'object': 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000', 
213             'applet': true 
214         }, 
215         // 判断是否已有缓存数据 
216         hasData: function(elem) { 
217             elem = elem.nodeType ? jQuery.cache[elem[jQuery.expando]] : elem[jQuery.expando]; 
218             return !!elem && !isEmptyDataObject(elem); 
219         }, 
220         // 适配器模式 
221         data: function(elem, name, data) { 
222             return internalData(elem, name, data); 
223         }, 
224         removeData: function(elem, name) { 
225             return internalRemoveData(elem, name); 
226         }, 
227         // 私有方法 
228         _data: function(elem, name, data) { 
229             return internalData(elem, name, data, true); 
230         }, 
231         _removeData: function(elem, name) { 
232             return internalRemoveData(elem, name, true); 
233         }, 
234         // 判断元素或对象是否可以缓存 
235         acceptData: function(elem) { 
236             if (elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9) { 
237                 return false; 
238             } 
239  
240             var noData = elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]; 
241  
242             return !noData || noData !== true && elem.getAttribute('classid') === noData; 
243         } 
244     }); 
245  
246     jQuery.fn.extend({ 
247         data: function(key, value) { 
248             var attrs, name, 
249                 elem = this[0], 
250                 i = 0, 
251                 data = null; 
252  
253             // 如果key为undefined,说明key和value都为空,获取缓存data 
254             if (key === undefined) { 
255                 // 如果有DOM元素 
256                 if (this.length) { 
257                     // 获取以前保存在elem的data 
258                     data = jQuery.data(elem); 
259  
260                     // 对于元素节点而言,数据可以来自两个地方: 
261                     // 1. jQuery.cache缓存中,之前手动存进去的,如:$dom.data('data1', value1); 
262                     // 2. 来自html标签的以data-开头的属性,之后该属性的数据也会被存储到jQuery.cache缓存中 
263  
264                     // 如果元素节点的jQuery.cache['parsedAttrs']的值为null | false | undefined 
265                     // 说明elem的属性节点没有被解析过,下面就进行解析 
266                     if (elem.nodeType === 1 && !jQuery._data(elem, 'parsedAttrs')) { 
267                         // 获得elem的属性列表 
268                         attrs = elem.attributes; 
269                         for (; i < attrs.length; i++) { 
270                             // 该属性名称 
271                             name = attrs[i].name; 
272  
273                             // 如果name有"data-"字符 
274                             if (!name.indexOf('data-')) { 
275                                 // 将name驼峰化:"dataCustom" 
276                                 name = jQuery.camelCase(name.slice(5)); 
277  
278                                 // 如果没有对应的缓存,就将html5的“data-”值(转换后)设置为相应的缓存值 
279                                 dataAttr(elem, name, data[name]); 
280                             } 
281                         } 
282                         // 给缓存对象添加私有缓存,并把缓存值设置为true 
283                         // 用来标记已经解析过属性 
284                         jQuery._data(elem, 'parseAttrs', true); 
285                     } 
286                 } 
287  
288                 return data; 
289             } 
290  
291             // 如果key是对象,直接将其拷贝到jQuery.cache.data缓存对象里 
292             // 用来设置多个值的情况 
293             if (typeof key === 'object') { 
294                 return this.each(function() { 
295                     jQuery.data(this, key); 
296                 }); 
297             } 
298  
299             // 为每个元素执行函数后返回原始的元素集(this) 
300             return jQuery.access(this, function(value) { 
301                 if (value === undefined) { 
302                     // 如果value未定义并且在jQuery.cache缓存中没有找到相应key的缓存, 
303                     // 然后再试图查看HTML5标签的“data-”属性是否被解析过了 
304                     return elem ? dataAttr(elem, key, jQuery.data(elem, key)) : null; 
305                 } 
306             }, null, value, arguments.length > 1, null, true); 
307         }, 
308         removeData: function(key) { 
309             return this.each(function() { 
310                 jQuery.removeData(this, key); 
311             }); 
312         } 
313     }); 
314  
315     // 处理元素节点中使用HTML5的“data-test”属性,并将其转换到相应的类型存储到jQuery.cache对象中 
316  
317     function dataAttr(elem, key, data) { 
318         // 如果data为空且elem是元素节点,那么将HTML5的data-属性值转换为相应的类型 
319         if (data === undefined && elem.nodeType === 1) { 
320             // 反驼峰化 
321             var name = 'data-' + key.replace(rmultiDash, '-$1').toLowerCase(); 
322  
323             // 获取data字符串属性值 
324             data = elem.getAttribute(name); 
325  
326             if (typeof data === 'string') { 
327                 try { 
328                     // 布尔型 
329                     data = data === 'true' ? true : 
330                         data === 'false' ? false : 
331                     // null 
332                     data === 'null' ? null : 
333                     // +data只会将数字字符转换成数字,再加上""则会转换回字符串 
334                     // 这里是测试是否为数字 
335                     +data + '' === data ? +data : 
336                     // 数组或对象,并转换 
337                     rbrace.test(data) ? jQuery.parseJSON(data) : 
338                     // 其他类型 
339                     data; 
340                 } catch (e) {} 
341  
342                 // 将格式化的数据存在jQuery.cache缓存。 
343                 jQuery.data(elem, key, data); 
344             } else { 
345                 // 如果该属性不存在,此时data为null,将其转换为undefined 
346                 data = undefined; 
347             } 
348         } 
349  
350         // 返回data-属性值(转换后)的类型 
351         return data; 
352     } 
353  
354     // 检查缓存对象的数据是否为空 
355  
356     function isEmptyDataObject(obj) { 
357         var name; 
358         for (name in obj) { 
359             // 如果公共data为空,那么私有对象也为空 
360             if (name === 'data' && jQuery.isEmptyObject(obj[name])) { 
361                 continue; 
362             } 
363             if (name !== 'toJSON') { 
364                 return false; 
365             } 
366         } 
367  
368         return true; 
369     }

本文参考链接:https://www.cnblogs.com/webFrontDev/archive/2013/05/29/3106894.html