jQuery的資料快取模塊以一種安全的方式為DOM元素附加任意型別的資料,避免了在JavaScript物件和DOM元素之間出現回圈參考,以及由此而導致的記憶體泄漏,
資料快取模塊為DOM元素和JavaScript物件提供了統一的資料設定、讀取和移除方法,在jQuery內部還為佇列模塊、影片模塊、樣式操作模塊、事件系統提供基礎功能,負責維護這些模塊運行時的內部資料,
writer by:大沙漠 QQ:22969969
對于DOM元素和JavaScript物件,資料的存盤位置是不同的,如下:
- 對于DOM元素jQuery直接把資料存盤在jQuery.cache中
- 對于JavaScript物件,垃圾回識訓制能夠自動發生,因此資料可以直接存盤在JavaScript物件中,
另外為了避免jQuery內部使用的資料和用戶自定義的資料發生沖突,分為內部資料快取物件和自定義資料快取物件
- 內部快取物件 ;jQuery內部使用 ;DOM元素:存盤在$.cache[elem[$.expando]] ;JavaScript物件:obj[$.expando]
- 自定義快取物件 ;給用戶使用的 ;DOM元素:存盤在$.cache[elem[$.expando]].data ;JavaScript物件:obj[$.expando].data
jQuery的靜態方法含有如下API:
- $.cache ;DOM元素的資料快取物件,所有DOM元素存盤的資料都會存盤在該物件里
- $.uuid ;唯一id種子,初始值為0,當資料存盤在DOM元素上時用到,元素的$.expando屬性的值等于最新的$.uuid加1
- $.expando ;頁面中每個jQuery副本的唯一標識,只有重繪頁面才會發生變化,格式:jQuery+版本號+亂數
- $.acceptData(elem) ;判斷DOM元素elem是否可以設定資料,elem是一個DOM節點
- $.hasData(elem) ;判斷elem是否有關聯的資料
- $.data(elem, name, data,pvt) ;設定或回傳DOM或JavaScript物件的資料,
·elem是DOM元素或JavaScript物件,
·name是要設定或讀取的資料名,也可以是包含鍵值對的物件,
·data是要設定的資料值,可以是任意資料,
·pvt表示操作的是否為內部資料,默認為false
- $._data(elem, name, data) ;設定、讀取內部資料,內部代碼就一句return jQuery.data( elem, name, data, true )
- $.removeData(elem, name, pvt) ;移除通過$.data()設定的資料,pvt表示是否為內部資料
- $.cleanData(elem) ;移除多個DOM元素的全部資料和事件
jQuery/$ 實體方法(可以通過jQuery實體呼叫的):
- data(key,value) ;設定/讀取自定義資料
- removeData(key) ;移除匹配元素的自定義資料,key可以是一個字符換或陣列,表示屬性或屬性串列
為DOM元素存盤資料時,比較特別,jQuery首先會在該DOM上添加一個名為$.expando的屬性,值是一個唯一的id,等于++$.uuid(jQuery的一個內置屬性),$.uuid是一個整型值,初始值為0,為該DOM添加屬性之后還會把這個id作為屬性添加到全域快取物件jQuery.cache中,對應的屬性值是一個JavaScript物件,該物件是DOM元素的資料快取物件
例如:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://libs.baidu.com/jquery/1.7.1/jquery.min.js"></script> </head> <body> <p>123</p> <script> var p = document.getElementsByTagName('p')[0]; $.data(p,'age',25,true); //設定內部資料age=25,這是直接定義在資料快取物件上的,等價于$._data(p,'age',25); $.data(p,'age',23); //設定自定義資料age=23,等價于$.data(p,'age',23,false),這是定義在資料快取物件的data屬性物件上 console.log($.data(p,undefined,undefined,true)); //輸出: Object { data=https://www.cnblogs.com/greatdesert/p/{ age=23}, age=25} ;獲取資料快取物件, console.log($.data(p)); //輸出: Object { age=23} ;獲取自定義快取物件,其實就是$.data(p,undefined,undefined,true)物件的data屬性 console.log($.cache[p[$.expando]].data === $.data(p)); //輸出true,從這里可以看出$.data(p)獲取的就是自定義快取物件,也就是資料快取物件的data屬性物件 </script> </body> </html>
輸出如下:

原始碼分析
對于資料快取模塊的靜態方法來說,它是以jQuery.extend({})函式直接掛載到jQuery里的,如下:
jQuery.extend({ cache: {}, //DOM元素的資料快取物件 // Please use with caution uuid: 0, // Unique for each copy of jQuery on the page // Non-digits removed to match rinlinejQuery expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), //頁面中每個jQuery副本的唯一標識 // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. noData: { //存放了不支持擴展屬性的embed、object、applet元素的節點名稱 "embed": true, // Ban all objects except for Flash (which handle expandos) "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", "applet": true }, hasData: function( elem ) { //判斷一個DOM元素或JavaScript物件是否有與之關聯的資料 elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; //如果是元素節點(有nodeType屬性)則判斷在jQuery.cache中是否有jQuery.expando屬性,否則認為是JavaScript物件,判斷是否有jQuery.expando屬性, return !!elem && !isEmptyDataObject( elem ); //如果elem存在且含有資料快取則回傳true,isEmptyDataObject是個jQuery內部的工具函式 }, data: function( elem, name, data, pvt /* Internal Use Only */ ) { /*略*/ }, removeData: function( elem, name, pvt /* Internal Use Only */ ) { /*略*/ }, // For internal use only. _data: function( elem, name, data ) { //設定、讀取內部資料,就是呼叫jQuery.data(),并設定第四個引數為true return jQuery.data( elem, name, data, true ); }, // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { //判斷引數elem是否可以設定資料,回傳true則可以設定,回傳false則不可以 if ( elem.nodeName ) { var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; if ( match ) { return !(match === true || elem.getAttribute("classid") !== match); } } return true; } });
我們主要看一下$.data()是怎么設定資料的,懂了怎么設定資料,removeData也就懂了,如下:
data: function( elem, name, data, pvt /* Internal Use Only */ ) { //設定、讀取自定義資料、內部資料 if ( !jQuery.acceptData( elem ) ) { //檢查elem元素是否支持設定資料,如果jQuery.acceptData()函式回傳false表示不允許設定資料 return; //則直接回傳,不繼續操作 } var privateCache, thisCache, ret, //privateCache默認指向資料快取物件(如果pvt引數未設定或者為false則指向自定義資料),thisCache表示自定義資料快取物件,如果pvt是true,則privateCache和thisCache都指向資料快取物件都指向資料快取物件,ret是讀取時的回傳值 internalKey = jQuery.expando, //jQuery.expando頁面中每個jQuery副本的唯一標識,把它賦值給internalKey是為了減少拼寫字數和縮短作用域鏈查找, getByName = typeof name === "string", //getByName表示name是否為字串 // We have to handle DOM nodes and JS objects differently because IE6-7 // can't GC object references properly across the DOM-JS boundary isNode = elem.nodeType, //isNode表示elem是否為DOM元素 // Only DOM nodes need the global jQuery cache; JS object data is // attached directly to the object so GC can occur automatically cache = isNode ? jQuery.cache : elem, //如果是DOM元素則存盤在$.cache中,如果是JavaScript物件則存盤在該物件本身 // Only defining an ID for JS objects if its cache already exists allows // the code to shortcut on the same path as a DOM node with no cache id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, isEvents = name === "events"; // Avoid doing any more work than we need to when trying to get data on an // object that has no data at all if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data =https://www.cnblogs.com/greatdesert/p/== undefined ) { //如果是讀取資料但沒有資料,則回傳,避免做不必要的作業,if陳述句中的符合運算式可以分兩個部分,后一部分是getByName && data =https://www.cnblogs.com/greatdesert/p/== undefined,表示,如果name是字串且data沒有設定,則說明是在讀資料, 前一部分(!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)表示,如果id不存在說明沒有該屬性,如果cache[id]不存在則說明沒有該資料 return; } /*執行到這里有兩種情況:1.存盤資料 2.讀取資料且資料存在*/ if ( !id ) { //如果id不存在,則分配一個 // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { //如果是DOM元素 elem[ internalKey ] = id = ++jQuery.uuid; //jQuery.uuid會自動加1,并附在DOM元素上 } else { id = internalKey; //否則關聯ID就是jQuery.expando } } if ( !cache[ id ] ) { //如果DOM物件或JavaScript物件對應的資料快取物件不存在則初始化為一個空物件 cache[ id ] = {}; // Avoids exposing jQuery metadata on plain JS objects when the object // is serialized using JSON.stringify if ( !isNode ) { cache[ id ].toJSON = jQuery.noop; } } // An object can be passed to jQuery.data instead of a key/value pair; this gets // shallow copied over onto the existing cache if ( typeof name === "object" || typeof name === "function" ) { //如果name是物件或者函式(函式好像不可以,只能是物件),則批量把引數name中的屬性合并到已有的資料快取物件上,即批量設定資料 if ( pvt ) { cache[ id ] = jQuery.extend( cache[ id ], name ); } else { cache[ id ].data = jQuery.extend( cache[ id ].data, name ); } } privateCache = thisCache = cache[ id ]; //設定privateCache和thisCache都指向資料快取物件cache[ id ] // jQuery data() is stored in a separate object inside the object's internal data // cache in order to avoid key collisions between internal data and user-defined // data. if ( !pvt ) { //如果引數pvt是false或者未設定,則設定thisCache指向自定義資料, if ( !thisCache.data ) { //如果資料快取物件thisCache.data不存在則先將其初始化為空物件, thisCache.data =https://www.cnblogs.com/greatdesert/p/ {}; } thisCache = thisCache.data; } if ( data !== undefined ) { //如果data不是undefined,則把引數data設定到屬性name上,這里統一把引數name轉換成了駝峰式,這樣在讀取的時候不管是連字串還是駝峰式就都不會出錯, thisCache[ jQuery.camelCase( name ) ] = data; } // Users should not attempt to inspect the internal events object using jQuery.data, // it is undocumented and subject to change. But does anyone listen? No. if ( isEvents && !thisCache[ name ] ) { return privateCache.events; } // Check for both converted-to-camel and non-converted data property names // If a data property was specified if ( getByName ) { //如果引數name是字串,則讀取單個資料 // First Try to find as-is property data ret = thisCache[ name ]; //先嘗試讀取引數name對應的資料 // Test for null|undefined property data if ( ret == null ) { //如果沒有讀取到則把引數name轉換為駝峰式再次嘗試讀取 // Try to find the camelCased property ret = thisCache[ jQuery.camelCase( name ) ]; } } else { ret = thisCache; //如果引數2不是字串,則回傳資料快取物件, } return ret; //最后回傳ret },
這樣就完成資料的設定的,對于jQuery實體上的方法如下:
jQuery.fn.extend({ data: function( key, value ) { //設定、讀取自定義資料,決議html5屬性data- key是要設定或讀取的資料名,或者是含有鍵值對的物件,value是要設定的資料值,可以是任意型別 var parts, attr, name, data = null; if ( typeof key === "undefined" ) { //如果未傳入引數,即引數格式是.data(),則獲取第一個匹配元素關聯的資料快取物件(即獲得全部資料) if ( this.length ) { //如果該jQuery物件有匹配的元素 data = https://www.cnblogs.com/greatdesert/p/jQuery.data( this[0] ); //獲取第一個元素的資料快取物件 if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { //這里是決議html5里的data-屬性的,可以先略過 attr = this[0].attributes; for ( var i = 0, l = attr.length; i < l; i++ ) { name = attr[i].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.substring(5) ); dataAttr( this[0], name, data[ name ] ); } } jQuery._data( this[0], "parsedAttrs", true ); //回傳第一個匹配元素關聯的自定義資料快取物件,如果沒有匹配元素則會回傳null } } return data; } else if ( typeof key === "object" ) { //如果key是一個物件,則為每個元素物件呼叫方法$.data(this,key)批量設定資料 return this.each(function() { jQuery.data( this, key ); }); } parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; //取出命名空間,比如$(this).data('a.b',123);則parts[1]是.b if ( value =https://www.cnblogs.com/greatdesert/p/== undefined ) { //如果傳入的格式是.data(key),則認為是讀取單個資料 data = https://www.cnblogs.com/greatdesert/p/this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); //觸發自定義事件getData,并把事件監聽函式的回傳值賦值給變數data // Try to fetch any internally stored data first if ( data =https://www.cnblogs.com/greatdesert/p/== undefined && this.length ) { //如果事件監聽函式沒有回傳值,才會嘗試從自定義資料快取物件中讀取 data = https://www.cnblogs.com/greatdesert/p/jQuery.data( this[0], key ); data = dataAttr( this[0], key, data ); } return data =https://www.cnblogs.com/greatdesert/p/== undefined && parts[1] ? //如果從getData()事件監聽函式或自定義資料快取物件或HTML5屬性data-中取到了資料,則回傳資料;如果沒有取到資料,但是指定了命名空間,則去掉命名空間再次嘗試讀取, this.data( parts[0] ) : data; } else { //如果傳入了引數key和value,即引數格式是:.data(key,value),則為每個匹配元素設定任意型別的資料,并觸發自定義事件setData()和changeData(), return this.each(function() { var self = jQuery( this ), args = [ parts[0], value ]; self.triggerHandler( "setData" + parts[1] + "!", args ); //觸發自定義事件setData,感嘆號表示只執行沒有命名控制元件的事件監聽函式 jQuery.data( this, key, value ); //呼叫$.data()方法為任意匹配元素設定任意型別的資料 self.triggerHandler( "changeData" + parts[1] + "!", args ); //觸發自定義事件changeData }); } }, /*...*/ })
設定資料快取就是這樣的,理解了設定資料快取,移除就很好理解了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/36826.html
標籤:jQuery
下一篇:jQuery 雜項方法大全
