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 }