【原創】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 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM