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={ 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 === undefined ) { //如果是读取数据但没有数据,则返回,避免做不必要的工作,if语句中的符合表达式可以分两个部分,后一部分是getByName && data === 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 = {};
} 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 = 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 === undefined ) { //如果传入的格式是.data(key),则认为是读取单个数据
data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); //触发自定义事件getData,并把事件监听函数的返回值赋值给变量data // 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] ? //如果从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
});
}
},
/*...*/
})

设置数据缓存就是这样的,理解了设置数据缓存,移除就很好理解了。

jQuery 源码分析(十) 数据缓存模块 data详解的更多相关文章

  1. jQuery源码分析(九) 异步队列模块 Deferred 详解

    deferred对象就是jQuery的回调函数解决方案,它解决了如何处理耗时操作的问题,比如一些Ajax操作,动画操作等.(P.s:紧跟上一节:https://www.cnblogs.com/grea ...

  2. jQuery 源码分析(十六) 事件系统模块 底层方法 详解

    jQuery事件系统并没有将事件监听函数直接绑定到DOM元素上,而是基于数据缓存模块来管理监听函数的,事件模块代码有点多,我把它分为了三个部分:分底层方法.实例方法和便捷方法.ready事件来讲,好理 ...

  3. jQuery 源码分析(十三) 数据操作模块 DOM属性 详解

    jQuery的属性操作模块总共有4个部分,本篇说一下第2个部分:DOM属性部分,用于修改DOM元素的属性的(属性和特性是不一样的,一般将property翻译为属性,attribute翻译为特性) DO ...

  4. Vue.js 源码分析(十二) 基础篇 组件详解

    组件是可复用的Vue实例,一个组件本质上是一个拥有预定义选项的一个Vue实例,组件和组件之间通过一些属性进行联系. 组件有两种注册方式,分别是全局注册和局部注册,前者通过Vue.component() ...

  5. Vue.js 源码分析(十) 基础篇 ref属性详解

    ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...

  6. jQuery 源码分析(十四) 数据操作模块 类样式操作 详解

    jQuery的属性操作模块总共有4个部分,本篇说一下第3个部分:类样式操作部分,用于修改DOM元素的class特性的,对于类样式操作来说,jQuery并没有定义静态方法,而只定义了实例方法,如下: a ...

  7. jQuery 源码分析(十二) 数据操作模块 html特性 详解

    jQuery的属性操作模块总共有4个部分,本篇说一下第1个部分:HTML特性部分,html特性部分是对原生方法getAttribute()和setAttribute()的封装,用于修改DOM元素的特性 ...

  8. jQuery 源码分析(十五) 数据操作模块 val详解

    jQuery的属性操作模块总共有4个部分,本篇说一下最后一个部分:val值的操作,也是属性操作里最简单的吧,只有一个API,如下: val(vlaue)        ;获取匹配元素集合中第一个元素的 ...

  9. jQuery 源码分析(十九) DOM遍历模块详解

    jQuery的DOM遍历模块对DOM模型的原生属性parentNode.childNodes.firstChild.lastChild.previousSibling.nextSibling进行了封装 ...

随机推荐

  1. java 初学 :求 s=a+aa+aaa+aaaa+aa...a 的值,其中 a 是一个数字。几个 数相加由键盘控制。

    import java.util.Scanner; public static void main(String[] args) {       Scanner input=new Scanner(S ...

  2. 项目如何部署在linux系统上

    前面已经安装好centos的系统,网络配置,以及部署的环境已成功啦... 下面记录的是如何部署一个项目 四个步骤: (1)放war包 (2)执行数据库脚本 (3)修改数据库的配置文件 (4)重启tom ...

  3. Codeforces Round #590 D. Distinct Characters Queries

    CF上给的标签是数据结构.但给的题解里的方法是很巧的暴力,用vector<set>维护每个字母出现的下标,每次修改加下标,擦下标.每次询问对每个字母分别lower_bound查找区间内是否 ...

  4. IT兄弟连 HTML5教程 HTML5做到了与之前版本的兼容

    为了保证HTML5能与之前的HTML版本达到最大的兼容,HTML5对一些元素标记的省略.boolean值的属性,以及引号的省略这几方面进行了兼顾,确保与之前版本的HTML达到兼容.在下面示例中,将本节 ...

  5. 【algo&ds】9.拓扑排序、AOV&AOE、关键路径问题

    对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性 ...

  6. nlohmann 最优秀的C++序列化工具库 详细入门教程

    本文首发于个人博客https://kezunlin.me/post/f3c3eb8/,欢迎阅读最新内容! tutorial to use nlohmann json for serializing d ...

  7. PAT 1011 World Cup Betting 查找元素

    With the 2010 FIFA World Cup running, football fans the world over were becoming increasingly excite ...

  8. Spring Boot AOP解析

    Spring Boot AOP 面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面. AOP(Aspec ...

  9. 使用文件流与使用缓冲流完成文件的复制操作性能对比,文件流 FileInputStream FileOutputStream 缓冲流: BufferedInputStream BufferedOutputStream

    package seday06; import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOExc ...

  10. CRM第一篇

    2.1搭建前提 我们在搭建CRM开发环境之前,需要明确2件事情: a.我们搭建环境采用基于注解的配置. b.搭建环境需要测试,我们以客户的保存和列表查询作为测试功能. 2.2搭建步骤 2.2.1导入S ...