jQuery數據緩存$.data 的使用以及源碼解析
阿新 • • 發佈:2018-12-10
query eric event rac 沒有 handle properly empty call
一、實現原理:
對於DOM元素,通過分配一個唯一的關聯id把DOM元素和該DOM元素的數據緩存對象關聯起來,關聯id被附加到以jQuery.expando的值命名的屬性上,數據存儲在全局緩存對象jQuery.cache中。在讀取、設置、移除數據時,將通過關聯id從全局緩存對象jQuery.cache中找到關聯的數據緩存對象,然後在數據緩存對象上執行讀取、設置、移除操作。
對於Javascript對象,數據則直接存儲在該Javascript對象的屬性jQuery.expando上。在讀取、設置、移除數據時,實際上是對Javascript對象的數據緩存對象執行讀取、設置、移除操作。
為了避免jQuery內部使用的數據和用戶自定義的數據發生沖突,數據緩存模塊把內部數據存儲在數據緩存對象上,把自定義數據存儲在數據緩存對象的屬性data上。
二、總體結構:
// 數據緩存 Data jQuery.extend({ // 全局緩存對象 cache: {}, // 唯一 id種子 uuid:0, // 頁面中每個jQuery副本的唯一標識 expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), // 是否有關聯的數據 hasData: function(){}, // 設置、讀取自定數據或內部數據 data: function(elem, name, data, pvt) {}, // 移除自定義數據或內部數據 removeData: function(elem, name, pvt) {}, // 設置、讀取內部數據 _data: function(elem, name, data) {}, // 是否可以設置數據 acceptData: function(elem){} }); jQuery.fn.extend({ // 設置、讀取自定義數據,解析HTML5屬性data- data: function(key,value){}, // 移除自定義數據 removeData: function(key){} }); // 解析HTML5屬性 data-functiondataAttr(elem,key,data){} // 檢查數據緩存對象是否為空functionisEmptyDataObject(obj){} jQuery.extend({ // 清空數據緩存對象 cleanData: function(elems){} });
三、$.data(elem, name, data), $.data(elem, name)
$.data(elem, name, data)的使用方法:
如果傳入參數name, data, 則設置任意類型的數據
<!doctype html> <htmllang="en"> <head> <metacharset="utf-8"> <title>jQuery.data demo</title> <style> div { color: blue; } span { color: red; } </style> <scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script> </head> <body> <div> The values stored were <span></span> and <span></span> </div> <script> var div = $( "div" )[ 0 ]; jQuery.data( div, "test", { first: 16, last: "pizza!" }); $( "span:first" ).text( jQuery.data( div, "test" ).first ); $( "span:last" ).text( jQuery.data( div, "test" ).last ); </script> </body> </html>
$.data(elem, name)的使用方法:
如果傳入key, 未傳入參數data, 則讀取並返回指定名稱的數據
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>jQuery.data demo</title>
<style>
div {
margin: 5px;
background: yellow;
}
button {
margin: 5px;
font-size: 14px;
}
p {
margin: 5px;
color: blue;
}
span {
color: red;
}
</style>
<scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<div>A div</div>
<button>Get "blah" from the div</button>
<button>Set "blah" to "hello"</button>
<button>Set "blah" to 86</button>
<button>Remove "blah" from the div</button>
<p>The "blah" value of this div is <span>?</span></p>
<script>
$( "button" ).click( function() {
var value,
div = $( "div" )[ 0 ];
switch ( $( "button" ).index( this ) ) {
case0 :
value = jQuery.data( div, "blah" );
break;
case1 :
jQuery.data( div, "blah", "hello" );
value = "Stored!";
break;
case2 :
jQuery.data( div, "blah", 86 );
value = "Stored!";
break;
case3 :
jQuery.removeData( div, "blah" );
value = "Removed!";
break;
}
$( "span" ).text( "" + value );
});
</script>
</body>
</html>
$.data(elem, name, data), $.data(elem, name) 源碼解析:
jQuery.extend({
// 1. 定義jQuery.data(elem, name, data, pvt)
data: function( elem, name, data, pvt /* Internal Use Only */ ) {
// 2. 檢查是否可以設置數據
if ( !jQuery.acceptData( elem ) ) {
return; // 如果參數elem不支持設置數據,則立即返回
}
// 3 定義局部變量
var privateCache, thisCache, ret,
internalKey = jQuery.expando,
getByName = typeof name === "string",
// 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, // 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元素,為了避免javascript和DOM元素之間循環引用導致的瀏覽器(IE6/7)垃圾回收機制不起作用,要把數據存儲在全局緩存對象jQuery.cache中;對於javascript對象,來及回收機制能夠自動發生,不會有內存泄露的問題,因此數據可以查收存儲在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 withnocache
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
// 4. 如果是讀取數據,但沒有數據,則返回
if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
return;
// getByName && data === undefined 如果name是字符串,data是undefined, 說明是在讀取數據
// !id || !cache[id] || (!isEvents && !pvt && !cache[id].data 如果關聯id不存在,說明沒有數據;如果cache[id]不存在,也說明沒有數據;如果是讀取自動以數據,但cache[id].data不存在,說明沒有自定義數據
}
// 5. 如果關聯id不存在,則分配一個
if ( !id ) {
// Only DOM nodes need a new unique ID foreach element since their data
// ends up in the globalcacheif ( isNode ) {
elem[ internalKey ] = id = ++jQuery.uuid; // 對於DOM元素,jQuery.uuid會自動加1,並附加到DOM元素上
} else {
id = internalKey; // 對於javascript對象,關聯id就是jQuery.expando
}
}
// 6. 如果數據緩存對象不存在,則初始化為空對象{}
if ( !cache[ id ] ) {
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; // 對於javascript對象,設置方法toJSON為空函數,以避免在執行JSON.stringify()時暴露緩存數據。如果一個對象定義了方法toJSON(),JSON.stringify()在序列化該對象時會調用這個方法來生成該對象的JSON元素
}
}
// An object can be passed to jQuery.data instead of a key/value pair; this gets
// shallow copied over onto the existing cache
// 7. 如果參數name是對象或函數,則批量設置數據
if ( typeof name === "object" || typeof name === "function" ) {
if ( pvt ) {
cache[ id ] = jQuery.extend( cache[ id ], name ); // 對於內部數據,把參數name中的屬性合並到cache[id]中
} else {
cache[ id ].data = jQuery.extend( cache[ id ].data, name ); // 對於自定義數據,把參數name中的屬性合並到cache[id].data中
}
}
// 8. 如果參數data不是undefined, 則設置單個數據
privateCache = thisCache = cache[ id ];
// jQuery data() is stored in a separate object inside the object‘s internal data
// cacheinorderto avoid key collisions between internal dataanduser-defined
// data.
if ( !pvt ) {
if ( !thisCache.data ) {
thisCache.data = {};
}
thisCache = thisCache.data;
}
if ( data !== undefined ) {
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.
// 9. 特殊處理eventsif ( isEvents && !thisCache[ name ] ) { // 如果參數name是字符串"events",並且未設置過自定義數據"events",則返回事件婚車對象,在其中存儲了事件監聽函數。
return privateCache.events;
}
// Checkforboth converted-to-camel and non-converted data property names
// If a data property was specified
//10. 如果參數name是字符串,則讀取單個數據
if ( getByName ) {
// 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 { // 11. 如果未傳入參數name,data,則返回數據緩存對象
ret = thisCache;
}
return ret;
},
// For internal useonly.
_data: function( elem, name, data ) {
return jQuery.data( elem, name, data, true );
},
});
四、.data(key, value), .data(key)
使用方法:
$( "body" ).data( "foo", 52 ); // 傳入key, value
$( "body" ).data( "bar", { myType: "test", count: 40 } ); // 傳入key, value
$( "body" ).data( { baz: [ 1, 2, 3 ] } ); // 傳入key, value
$( "body" ).data( "foo" ); // 52 // 傳入key
$( "body" ).data(); // 未傳入參數
HTML5 data attriubutes:
<div data-role="page" data-last-value="43" data-hidden="true" data-options=‘{"name":"John"}‘></div>
$( "div" ).data( "role" ) === "page";
$( "div" ).data( "lastValue" ) === 43;
$( "div" ).data( "hidden" ) === true;
$( "div" ).data( "options" ).name === "John";
.data(key, value), .data(key) 源碼解析
jQuery.fn.extend({
// 1. 定義.data(key, value)
data: function( key, value ) {
var parts, attr, name,
data = null;
// 2. 未傳入參數的情況if ( typeof key === "undefined" ) {
if ( this.length ) { // 如果參數key是undefined, 即參數格式是.data(), 則調用方法jQuery.data(elem, name, data, pvt)獲取第一個匹配元素關聯的自定義數據緩存對象,並返回。
data = jQuery.data( this[0] );
if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
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 );
}
}
return data;
// 3. 參數key 是對象的情況,即參數格式是.data(key),則遍歷匹配元素集合,為每個匹配元素調用方法jQuery.data(elem, name, data,pvt)批量設置數據
} elseif ( typeof key === "object" ) {
returnthis.each(function() {
jQuery.data( this, key );
});
}
// 4. 只傳入參數key的情況 如果只傳入參數key, 即參數格式是.data(key),則返回第一個匹配元素的指定名稱數據
parts = key.split(".");
parts[1] = parts[1] ? "." + parts[1] : "";
if ( value === undefined ) {
data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
// Try to fetch any internally stored data firstif ( data === undefined && this.length ) {
data = jQuery.data( this[0], key );
data = dataAttr( this[0], key, data );
}
return data === undefined && parts[1] ?
this.data( parts[0] ) :
data;
// 5. 傳入參數key和value的情況 即參數格式是.data(key, value),則為每個匹配元素設置任意類型的數據,並觸發自定義事件setData, changeData
} else {
returnthis.each(function() {
var self = jQuery( this ),
args = [ parts[0], value ];
self.triggerHandler( "setData" + parts[1] + "!", args );
jQuery.data( this, key, value );
self.triggerHandler( "changeData" + parts[1] + "!", args );
});
}
},
removeData: function( key ) {
returnthis.each(function() {
jQuery.removeData( this, key );
});
}
});
// 6. 函數dataAttr(elem, key, data)解析HTML5屬性data-functiondataAttr( elem, key, data ) {
// If nothing was found internally, try to fetch any// data from the HTML5 data-* attribute// 只有參數data為undefined時,才會解析HTML5屬性data-if ( data === undefined && elem.nodeType === 1 ) {
var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
data = elem.getAttribute( name );
if ( typeof data === "string" ) {
try {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
jQuery.isNumeric( data ) ? parseFloat( data ) :
rbrace.test( data ) ? jQuery.parseJSON( data ) :
data;
} catch( e ) {}
// Make sure we set the data so it isn‘t changed later
jQuery.data( elem, key, data );
} else {
data = undefined;
}
}
return data;
}
五、$.removeData(elem, name),.removeData(key)
使用方法:
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>jQuery.removeData demo</title>
<style>
div {
margin: 2px;
color: blue;
}
span {
color: red;
}
</style>
<scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<div>value1 before creation: <span></span></div>
<div>value1 after creation: <span></span></div>
<div>value1 after removal: <span></span></div>
<div>value2 after removal: <span></span></div>
<script>
var div = $( "div" )[ 0 ];
$( "span:eq(0)" ).text( "" + $( "div" ).data( "test1" ) ); //undefined
jQuery.data( div, "test1", "VALUE-1" );
jQuery.data( div, "test2", "VALUE-2" );
$( "span:eq(1)" ).text( "" + jQuery.data( div, "test1" ) ); // VALUE-1
jQuery.removeData( div, "test1" );
$( "span:eq(2)" ).text( "" + jQuery.data( div, "test1" ) ); // undefined
$( "span:eq(3)" ).text( "" + jQuery.data( div, "test2" ) ); // value2
</script>
</body>
</html>
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>removeData demo</title>
<style>
div {
margin: 2px;
color: blue;
}
span {
color: red;
}
</style>
<scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<div>value1 before creation: <span></span></div>
<div>value1 after creation: <span></span></div>
<div>value1 after removal: <span></span></div>
<div>value2 after removal: <span></span></div>
<script>
$( "span:eq(0)" ).text( "" + $( "div" ).data( "test1" ) ); // undefined
$( "div" ).data( "test1", "VALUE-1" );
$( "div" ).data( "test2", "VALUE-2" );
$( "span:eq(1)" ).text( "" + $( "div").data( "test1" ) ); // VALUE-1
$( "div" ).removeData( "test1" );
$( "span:eq(2)" ).text( "" + $( "div" ).data( "test1" ) ); // undefined
$( "span:eq(3)" ).text( "" + $( "div" ).data( "test2" ) ); // VALUE-2
</script>
</body>
</html>
$.removeData(elem, name),.removeData(key) 源碼解析:
$.extend({
// jQuery.removeData(elem,name,pvt)用於移除通過jQuery.data()設置的數據
removeData: function( elem, name, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var thisCache, i, l,
// Reference to internal data cache key
internalKey = jQuery.expando,
isNode = elem.nodeType,
// See jQuery.data for more information
cache = isNode ? jQuery.cache : elem,
// See jQuery.data for more information
id = isNode ? elem[ internalKey ] : internalKey;
// If there is already no cache entry for this object, there is no// purpose in continuingif ( !cache[ id ] ) {
return;
}
// 如果傳入參數name, 則移除一個或多個數據if ( name ) {
thisCache = pvt ? cache[ id ] : cache[ id ].data;
if ( thisCache ) { // 只有數據緩存對象thisCache存在時,才有必要移除數據// Support array or space separated string names for data keysif ( !jQuery.isArray( name ) ) {
// try the string as a key before any manipulationif ( name in thisCache ) {
name = [ name ];
} else {
// split the camel cased version by spaces unless a key with the spaces exists
name = jQuery.camelCase( name );
if ( name in thisCache ) {
name = [ name ];
} else {
name = name.split( " " );
}
}
}
// 遍歷參數name中的數據名,用運算符delete逐個從數據緩存對象thisCache中移除for ( i = 0, l = name.length; i < l; i++ ) {
delete thisCache[ name[i] ];
}
// If there is no data left in the cache, we want to continue// and let the cache object itself get destroyedif ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
return;
}
}
}
// See jQuery.data for more information// 刪除自定義數據緩存對象cache[id].dataif ( !pvt ) {
delete cache[ id ].data;
// Don‘t destroy the parent cache unless the internal data object// had been the only thing left in itif ( !isEmptyDataObject(cache[ id ]) ) {
return;
}
}
// Browsers that fail expando deletion also refuse to delete expandos on// the window, but it will allow it on all other JS objects; other browsers// don‘t care// Ensure that `cache` is not a window object #10080// 刪除數據緩存對象cache[id]if ( jQuery.support.deleteExpando || !cache.setInterval ) {
delete cache[ id ];
} else {
cache[ id ] = null;
}
// We destroyed the cache and need to eliminate the expando on the node to avoid// false lookups in the cache for entries that no longer exist// 刪除DOM元素上擴展的jQuery.expando屬性if ( isNode ) {
// IE does not allow us to delete expando properties from nodes,// nor does it have a removeAttribute function on Document nodes;// we must handle all of these casesif ( jQuery.support.deleteExpando ) {
delete elem[ internalKey ];
} elseif ( elem.removeAttribute ) {
elem.removeAttribute( internalKey );
} else {
elem[ internalKey ] = null;
}
}
}
});
jQuery.fn.extend({
removeData: function( key ) {
returnthis.each(function() {
jQuery.removeData( this, key );
});
}
});
// checks a cache object for emptinessfunctionisEmptyDataObject( obj ) {
for ( var name in obj ) {
// if the public data object is empty, the private is still emptyif ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
continue;
}
if ( name !== "toJSON" ) {
return false;
}
}
return true;
}
六、$.hasData(elem)
使用方法:
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>jQuery.hasData demo</title>
<scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<p>Results: </p>
<script>
var $p = jQuery( "p" ), p = $p[ 0 ];
$p.append( jQuery.hasData( p ) + " " ); // false
$.data( p, "testing", 123 );
$p.append( jQuery.hasData( p ) + " " ); // true
$.removeData( p, "testing" );
$p.append( jQuery.hasData( p ) + " " ); // false
$p.on( "click", function() {} );
$p.append( jQuery.hasData( p ) + " " ); // true
$p.off( "click" );
$p.append( jQuery.hasData( p ) + " " ); // false
</script>
</body>
</html>
$.hasData(elem) 源碼解析:
$.extend({
hasData: function( elem ) {
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
return !!elem && !isEmptyDataObject( elem );
// 如果關聯的數據緩存對象存在,並且含有數據,則返回true, 否則返回false。 這裏用兩個邏輯非運算符! 把變量elem轉換為布爾值
}
});
jQuery數據緩存$.data 的使用以及源碼解析