一、实现原理:
对于DOM元素,通过分配一个唯一的关联id把DOM元素和该DOM元素的数据缓存对象关联起来,关联id被附加到以jQuery.expando的值命名的属性上,数据存储在全局缓存对象jQuery.cache中。在读取、设置、移除数据时,将通过关联id从全局缓存对象jQuery.cache中找到关联的数据缓存对象,然后在数据缓存对象上执行读取、设置、移除操作。
对于Javascript对象,数据则直接存储在该Javascript对象的属性jQuery.expando上。在读取、设置、移除数据时,实际上是对Javascript对象的数据缓存对象执行读取、设置、移除操作。
为了避免jQuery内部使用的数据和用户自定义的数据发生冲突,数据缓存模块把内部数据存储在数据缓存对象上,把自定义数据存储在数据缓存对象的属性data上。
二、总体结构:
// 数据缓存 DatajQuery.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" ) ); //undefinedjQuery.data( div, "test1", "VALUE-1" );jQuery.data( div, "test2", "VALUE-2" );$( "span:eq(1)" ).text( "" + jQuery.data( div, "test1" ) ); // VALUE-1jQuery.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 的使用以及源码解析
原文地址:https://www.cnblogs.com/both-eyes/p/10099135.html