jQuery中数据缓存$.data的用法及源码完全解析
来源: 阅读:975 次 日期:2016-07-06 11:34:30
温馨提示: 小编为您整理了“jQuery中数据缓存$.data的用法及源码完全解析”,方便广大网友查阅!

这篇文章主要介绍了jQuery中的数据缓存$.data的用法及源码完全解析,深入解读了jQuery对缓存对象的读写和移除的实现,需要的朋友可以参考下

一、实现原理:

对于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-

function dataAttr(elem,key,data){}

// 检查数据缓存对象是否为空

function isEmptyDataObject(obj){}

jQuery.extend({

// 清空数据缓存对象

cleanData: function(elems){}

});

三、$.data(elem, name, data), $.data(elem, name)

$.data(elem, name, data)的使用方法:

如果传入参数name, data, 则设置任意类型的数据

jQuery.data demo

The values stored were

and

Baidu
map

$.data(elem, name)的使用方法:

如果传入key, 未传入参数data, 则读取并返回指定名称的数据

jQuery.data demo

A div

The "blah" value of this div is ?

Baidu
map

$.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 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

// 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 for each element since their data

// ends up in the global cache

if ( 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

// cache in order to avoid key collisions between internal data and user-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. 特殊处理events

if ( isEvents && !thisCache[ name ] ) { // 如果参数name是字符串"events",并且未设置过自定义数据"events",则返回事件婚车对象,在其中存储了事件监听函数。

return privateCache.events;

}

// Check for both 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 use only.

_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";

$( "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)批量设置数据

} else if ( typeof key === "object" ) {

return this.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 first

if ( 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 {

return this.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 ) {

return this.each(function() {

jQuery.removeData( this, key );

});

}

});

// 6. 函数dataAttr(elem, key, data)解析HTML5属性data-

function dataAttr( 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)

使用方法:

jQuery.removeData demo

value1 before creation:

value1 after creation:

value1 after removal:

value2 after removal:

Baidu
map

removeData demo

value1 before creation:

value1 after creation:

value1 after removal:

value2 after removal:

Baidu
map

$.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 continuing

if ( !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 keys

if ( !jQuery.isArray( name ) ) {

// try the string as a key before any manipulation

if ( 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 destroyed

if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {

return;

}

}

}

// See jQuery.data for more information

// 删除自定义数据缓存对象cache[id].data

if ( !pvt ) {

delete cache[ id ].data;

// Don't destroy the parent cache unless the internal data object

// had been the only thing left in it

if ( !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 cases

if ( jQuery.support.deleteExpando ) {

delete elem[ internalKey ];

} else if ( elem.removeAttribute ) {

elem.removeAttribute( internalKey );

} else {

elem[ internalKey ] = null;

}

}

}

});

jQuery.fn.extend({

removeData: function( key ) {

return this.each(function() {

jQuery.removeData( this, key );

});

}

});

// checks a cache object for emptiness

function isEmptyDataObject( obj ) {

for ( var name in obj ) {

// if the public data object is empty, the private is still empty

if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {

continue;

}

if ( name !== "toJSON" ) {

return false;

}

}

return true;

}

六、$.hasData(elem)

使用方法:

jQuery.hasData demo

Results:

Baidu
map

$.hasData(elem) 源码解析:

$.extend({

hasData: function( elem ) {

elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];

return !!elem && !isEmptyDataObject( elem );

// 如果关联的数据缓存对象存在,并且含有数据,则返回true, 否则返回false。 这里用两个逻辑非运算符! 把变量elem转换为布尔值

}

});

更多信息请查看 网络编程
由于各方面情况的不断调整与变化, 提供的所有考试信息和咨询回复仅供参考,敬请考生以权威部门公布的正式信息和咨询为准!

2026上岸·考公考编培训报班

  • 报班类型
  • 姓名
  • 手机号
关于我们| 联系我们| 人才招聘| 网站声明| 网站帮助| 非正式的简要咨询| 简要咨询须知| 新媒体/短视频平台| 手机站点| 投诉建议
工业和信息化部备案号:滇ICP备2023014141号-1 云南省教育厅备案号:云教ICP备0901021 滇公网安备53010202001879号 人力资源服务许可证:(云)人服证字(2023)第0102001523号
云南网警备案专用图标
联系电话:0871-65099533/13759567129 获取招聘考试信息及咨询关注公众号:
咨询QQ:1093837350(9:00—18:00) 版权所有:
云南网警报警专用图标
Baidu
map