在实际应用中, 我们经常需要往节点中缓存一些数据. 这些数据往往和dom元素紧密相关. dom节点也是对象, 所以我们可以直接扩展dom节点的属性. 不过肆意污染dom节点是不良少年的行为. 我们需要一种低耦合的方式让dom和缓存数据能够联系起来.
jquery提供了一套非常巧妙的缓存办法.
我们先在jquery内部创建一个cache对象{}, 来保存缓存数据.
然后往需要进行缓存的dom节点上扩展一个值为expando的属性, 这里是”jquery” + (new Date).getTime().
接着把每个节点的dom[expando]的值都设为一个自增的变量id,保持全局唯一性.
这个id的值就作为cache的key用来关联dom节点和数据.
也就是说cache[id]就取到了这个节点上的所有缓存.
而每个元素的所有缓存都被放到了一个map里面,这样可以同时缓存多个数据.
比如有2个节点dom1和dom2, 它们的缓存数据在cache中的格式应该是这样
Java代码
cache = {
dom1[expando]: {
key1: value1,
key2: value2
},
dom2[expando]: {
key3: value3,
key4: value4
}
}
cache = {
dom1[expando]: {
key1: value1,
key2: value2
},
dom2[expando]: {
key3: value3,
key4: value4
}
}
expando的值等于”jquery”+当前时间, 元素本身具有这种属性而起冲突的情况是微乎其微的.
我们在看源码之前, 先根据上面的原理来自己实现一个简单的缓存系统.以便增强理解.
先把跟data相关的所有代码都封装到一个闭包里,通过返回的接口暴露给外界.
同时为了简便,我们拆分成setData和getData两个方法.
Java代码
<div id="ddd">dddddd</div>
<script>
var Data = function(){
var cache = {};
var expando = "zengtan" + +new Date;
var uuid = 1;
var setData = function(elem, key, value){
var id = elem[expando];
if (!id){ //第一次给元素设置缓存
id = ++uuid;
elem[expando] = id;
}
if (!cache[id]){ //这个元素第一次进行缓存或者缓存已被清空
cache[id] = {};
}
cache[id][key] = value;
};
var getData = function(elem, key){
var id = elem[expando]; //取得cache里跟dom节点关联的key
return cache[id] && cache[id][key] || null; //如果缓存里没有, 返回null
}
return {
setData: setData,
getData: getData
}
}()
</script>
var div = document.getElementById("ddd");
Data.setData(div, "name", "zengtan");
var value = Data.getData(div, "name");
alert (value)
<div id="ddd">dddddd</div>
<script>
var Data = function(){
var cache = {};
var expando = "zengtan" + +new Date;
var uuid = 1;
var setData = function(elem, key, value){
var id = elem[expando];
if (!id){ //第一次给元素设置缓存
id = ++uuid;
elem[expando] = id;
}
if (!cache[id]){ //这个元素第一次进行缓存或者缓存已被清空
cache[id] = {};
}
cache[id][key] = value;
};
var getData = function(elem, key){
var id = elem[expando]; //取得cache里跟dom节点关联的key
return cache[id] && cache[id][key] || null; //如果缓存里没有, 返回null
}
return {
setData: setData,
getData: getData
}
}()
</script>
var div = document.getElementById("ddd");
Data.setData(div, "name", "zengtan");
var value = Data.getData(div, "name");
alert (value)
看看源码实现.
首先声明一些特殊的节点, 在它们身上存属性的时候可能会抛出异常.
Java代码
noData: {
embed: true,
object: true,
applet: true
}
jquery.data
data: function( elem, name, data ) {
if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
//如果是上面几个noData的节点类型
return;
}
elem = elem == window ?
//处理elem是window的情况, 如果不单独处理的话, 等于增加了一个全局变量, windowData也是一个{}
windowData :
elem;
var id = elem[ expando ], cache = jQuery.cache, thisCache;
/*
因为存数据的时候, 会给elem[ expando ]设置一个全局唯一标志量.判断id是否为undefined,就
知道已经有没有往这个元素上缓存过数据.
*/
if ( !id && typeof name === "string" && data === undefined ) {
//如果没有缓存, 而现在又是get方式. 返回null
return null;
}
if ( !id ) {
//第一次进行缓存, 分配一个全局唯一标志id
id = ++uuid;
}
if ( typeof name === "object" ) { //如果key是对象类型
elem[ expando ] = id; //分配id
thisCache = cache[ id ] = jQuery.extend(true, {}, name);
/*把整个对象做为cache[ id ]的值储存, 比如
$('#ddd').data({
"v2": "bbb",
"v3": "ccc";
});
相当于$('#ddd').data("v2", "bbb").data("v3":"ccc");
不过这样会清空该元素以前的data, 不理解为什么要这样做.
觉得这样才是更好的处理
jQuery.extend(cache[ id ], name);
*/
} else if ( !cache[ id ] ) {
//如果cache[ id ]中没有, 表示这个元素第一次进行缓存或者缓存已被清空
elem[ expando ] = id;
cache[ id ] = {};
//把cache[ id ]设置为一个map.
}
thisCache = cache[ id ];
if ( data !== undefined ) {
/*
set操作, 也可以防止一些意外的情况下缓存被清空. 比如data未定义的情况下, 缓存操作是无效的.
var a = {};
var b;
$(a).data("c";, 3);
$(a).data("c", b); b为undefined. 这句是无效的. 要移除缓存可以用removeData方法.
*/
thisCache[ name ] = data;
//即cache[ id ][ name ] = data, 把data设置进cache缓存对象中, 前面分配的自增id当做key来关联
}
return typeof name === "string" ? thisCache[ name ] : thisCache;
//如果key是string类型, 返回key对应的缓存, 否则返回整个元素上的缓存
}
noData: {
embed: true,
object: true,
applet: true
}
jquery.data
data: function( elem, name, data ) {
if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
//如果是上面几个noData的节点类型
return;
}
elem = elem == window ?
//处理elem是window的情况, 如果不单独处理的话, 等于增加了一个全局变量, windowData也是一个{}
windowData :
elem;
var id = elem[ expando ], cache = jQuery.cache, thisCache;
/*
因为存数据的时候, 会给elem[ expando ]设置一个全局唯一标志量.判断id是否为undefined,就
知道已经有没有往这个元素上缓存过数据.
*/
if ( !id && typeof name === "string" && data === undefined ) {
//如果没有缓存, 而现在又是get方式. 返回null
return null;
}
if ( !id ) {
//第一次进行缓存, 分配一个全局唯一标志id
id = ++uuid;
}
if ( typeof name === "object" ) { //如果key是对象类型
elem[ expando ] = id; //分配id
thisCache = cache[ id ] = jQuery.extend(true, {}, name);
/*把整个对象做为cache[ id ]的值储存, 比如
$('#ddd').data({
"v2": "bbb",
"v3": "ccc";
});
相当于$('#ddd').data("v2", "bbb").data("v3":"ccc");
不过这样会清空该元素以前的data, 不理解为什么要这样做.
觉得这样才是更好的处理
jQuery.extend(cache[ id ], name);
*/
} else if ( !cache[ id ] ) {
//如果cache[ id ]中没有, 表示这个元素第一次进行缓存或者缓存已被清空
elem[ expando ] = id;
cache[ id ] = {};
//把cache[ id ]设置为一个map.
}
thisCache = cache[ id ];
if ( data !== undefined ) {
/*
set操作, 也可以防止一些意外的情况下缓存被清空. 比如data未定义的情况下, 缓存操作是无效的.
var a = {};
var b;
$(a).data("c";, 3);
$(a).data("c", b); b为undefined. 这句是无效的. 要移除缓存可以用removeData方法.
*/
thisCache[ name ] = data;
//即cache[ id ][ name ] = data, 把data设置进cache缓存对象中, 前面分配的自增id当做key来关联
}
return typeof name === "string" ? thisCache[ name ] : thisCache;
//如果key是string类型, 返回key对应的缓存, 否则返回整个元素上的缓存
}
理论上讲, data方法可以用于任何对象的缓存, 不过如果是本地对象, 在内存泄露和继承的问题上都会遇到麻烦, 这里不详述, 所以一般我们还是用于dom节点.
我们用jquery缓存系统的时候, 一般调用的是prototype方法, prototype方法除了调用上面的静态方法之外. 还加入了对节点上自定义事件的处理, 留在event部分再讲.
当然, 我们还需要删除缓存的方法. 现在看看removeData的代码
Java代码
jQuery.removeData
removeData: function( elem, name ) {
if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
//同data方法
return;
}
elem = elem == window ?
windowData :
elem; //同data方法
var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ];
if ( name ) { //如果有指定的key
if ( thisCache ) { //如果元素有缓存
delete thisCache[ name ]; //直接delete掉
if ( jQuery.isEmptyObject(thisCache) ) {
//如果thisCache是个空对象, 说明所有缓存的数据都已经被删掉了.
jQuery.removeData( elem );
/*
重新调用一次removeData方法, 删掉缓存系统里已经无用的东西,
防止内存泄露, 注意现在走的是下面else分支.
*/
}
}
} else {
if ( jQuery.support.deleteExpando ) {
//如果支持delete, 见特性检测部分.
delete elem[ jQuery.expando ]; //删掉元素的expando属性
} else if ( elem.removeAttribute ) {
//如果支持removeAttribute
elem.removeAttribute( jQuery.expando );
}
delete cache[ id ]; //全局缓存里删除以这个id为key的对象
}
}
});
jquery提供了一套非常巧妙的缓存办法.
我们先在jquery内部创建一个cache对象{}, 来保存缓存数据.
然后往需要进行缓存的dom节点上扩展一个值为expando的属性, 这里是”jquery” + (new Date).getTime().
接着把每个节点的dom[expando]的值都设为一个自增的变量id,保持全局唯一性.
这个id的值就作为cache的key用来关联dom节点和数据.
也就是说cache[id]就取到了这个节点上的所有缓存.
而每个元素的所有缓存都被放到了一个map里面,这样可以同时缓存多个数据.
比如有2个节点dom1和dom2, 它们的缓存数据在cache中的格式应该是这样
Java代码
cache = {
dom1[expando]: {
key1: value1,
key2: value2
},
dom2[expando]: {
key3: value3,
key4: value4
}
}
cache = {
dom1[expando]: {
key1: value1,
key2: value2
},
dom2[expando]: {
key3: value3,
key4: value4
}
}
expando的值等于”jquery”+当前时间, 元素本身具有这种属性而起冲突的情况是微乎其微的.
我们在看源码之前, 先根据上面的原理来自己实现一个简单的缓存系统.以便增强理解.
先把跟data相关的所有代码都封装到一个闭包里,通过返回的接口暴露给外界.
同时为了简便,我们拆分成setData和getData两个方法.
Java代码
<div id="ddd">dddddd</div>
<script>
var Data = function(){
var cache = {};
var expando = "zengtan" + +new Date;
var uuid = 1;
var setData = function(elem, key, value){
var id = elem[expando];
if (!id){ //第一次给元素设置缓存
id = ++uuid;
elem[expando] = id;
}
if (!cache[id]){ //这个元素第一次进行缓存或者缓存已被清空
cache[id] = {};
}
cache[id][key] = value;
};
var getData = function(elem, key){
var id = elem[expando]; //取得cache里跟dom节点关联的key
return cache[id] && cache[id][key] || null; //如果缓存里没有, 返回null
}
return {
setData: setData,
getData: getData
}
}()
</script>
var div = document.getElementById("ddd");
Data.setData(div, "name", "zengtan");
var value = Data.getData(div, "name");
alert (value)
<div id="ddd">dddddd</div>
<script>
var Data = function(){
var cache = {};
var expando = "zengtan" + +new Date;
var uuid = 1;
var setData = function(elem, key, value){
var id = elem[expando];
if (!id){ //第一次给元素设置缓存
id = ++uuid;
elem[expando] = id;
}
if (!cache[id]){ //这个元素第一次进行缓存或者缓存已被清空
cache[id] = {};
}
cache[id][key] = value;
};
var getData = function(elem, key){
var id = elem[expando]; //取得cache里跟dom节点关联的key
return cache[id] && cache[id][key] || null; //如果缓存里没有, 返回null
}
return {
setData: setData,
getData: getData
}
}()
</script>
var div = document.getElementById("ddd");
Data.setData(div, "name", "zengtan");
var value = Data.getData(div, "name");
alert (value)
看看源码实现.
首先声明一些特殊的节点, 在它们身上存属性的时候可能会抛出异常.
Java代码
noData: {
embed: true,
object: true,
applet: true
}
jquery.data
data: function( elem, name, data ) {
if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
//如果是上面几个noData的节点类型
return;
}
elem = elem == window ?
//处理elem是window的情况, 如果不单独处理的话, 等于增加了一个全局变量, windowData也是一个{}
windowData :
elem;
var id = elem[ expando ], cache = jQuery.cache, thisCache;
/*
因为存数据的时候, 会给elem[ expando ]设置一个全局唯一标志量.判断id是否为undefined,就
知道已经有没有往这个元素上缓存过数据.
*/
if ( !id && typeof name === "string" && data === undefined ) {
//如果没有缓存, 而现在又是get方式. 返回null
return null;
}
if ( !id ) {
//第一次进行缓存, 分配一个全局唯一标志id
id = ++uuid;
}
if ( typeof name === "object" ) { //如果key是对象类型
elem[ expando ] = id; //分配id
thisCache = cache[ id ] = jQuery.extend(true, {}, name);
/*把整个对象做为cache[ id ]的值储存, 比如
$('#ddd').data({
"v2": "bbb",
"v3": "ccc";
});
相当于$('#ddd').data("v2", "bbb").data("v3":"ccc");
不过这样会清空该元素以前的data, 不理解为什么要这样做.
觉得这样才是更好的处理
jQuery.extend(cache[ id ], name);
*/
} else if ( !cache[ id ] ) {
//如果cache[ id ]中没有, 表示这个元素第一次进行缓存或者缓存已被清空
elem[ expando ] = id;
cache[ id ] = {};
//把cache[ id ]设置为一个map.
}
thisCache = cache[ id ];
if ( data !== undefined ) {
/*
set操作, 也可以防止一些意外的情况下缓存被清空. 比如data未定义的情况下, 缓存操作是无效的.
var a = {};
var b;
$(a).data("c";, 3);
$(a).data("c", b); b为undefined. 这句是无效的. 要移除缓存可以用removeData方法.
*/
thisCache[ name ] = data;
//即cache[ id ][ name ] = data, 把data设置进cache缓存对象中, 前面分配的自增id当做key来关联
}
return typeof name === "string" ? thisCache[ name ] : thisCache;
//如果key是string类型, 返回key对应的缓存, 否则返回整个元素上的缓存
}
noData: {
embed: true,
object: true,
applet: true
}
jquery.data
data: function( elem, name, data ) {
if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
//如果是上面几个noData的节点类型
return;
}
elem = elem == window ?
//处理elem是window的情况, 如果不单独处理的话, 等于增加了一个全局变量, windowData也是一个{}
windowData :
elem;
var id = elem[ expando ], cache = jQuery.cache, thisCache;
/*
因为存数据的时候, 会给elem[ expando ]设置一个全局唯一标志量.判断id是否为undefined,就
知道已经有没有往这个元素上缓存过数据.
*/
if ( !id && typeof name === "string" && data === undefined ) {
//如果没有缓存, 而现在又是get方式. 返回null
return null;
}
if ( !id ) {
//第一次进行缓存, 分配一个全局唯一标志id
id = ++uuid;
}
if ( typeof name === "object" ) { //如果key是对象类型
elem[ expando ] = id; //分配id
thisCache = cache[ id ] = jQuery.extend(true, {}, name);
/*把整个对象做为cache[ id ]的值储存, 比如
$('#ddd').data({
"v2": "bbb",
"v3": "ccc";
});
相当于$('#ddd').data("v2", "bbb").data("v3":"ccc");
不过这样会清空该元素以前的data, 不理解为什么要这样做.
觉得这样才是更好的处理
jQuery.extend(cache[ id ], name);
*/
} else if ( !cache[ id ] ) {
//如果cache[ id ]中没有, 表示这个元素第一次进行缓存或者缓存已被清空
elem[ expando ] = id;
cache[ id ] = {};
//把cache[ id ]设置为一个map.
}
thisCache = cache[ id ];
if ( data !== undefined ) {
/*
set操作, 也可以防止一些意外的情况下缓存被清空. 比如data未定义的情况下, 缓存操作是无效的.
var a = {};
var b;
$(a).data("c";, 3);
$(a).data("c", b); b为undefined. 这句是无效的. 要移除缓存可以用removeData方法.
*/
thisCache[ name ] = data;
//即cache[ id ][ name ] = data, 把data设置进cache缓存对象中, 前面分配的自增id当做key来关联
}
return typeof name === "string" ? thisCache[ name ] : thisCache;
//如果key是string类型, 返回key对应的缓存, 否则返回整个元素上的缓存
}
理论上讲, data方法可以用于任何对象的缓存, 不过如果是本地对象, 在内存泄露和继承的问题上都会遇到麻烦, 这里不详述, 所以一般我们还是用于dom节点.
我们用jquery缓存系统的时候, 一般调用的是prototype方法, prototype方法除了调用上面的静态方法之外. 还加入了对节点上自定义事件的处理, 留在event部分再讲.
当然, 我们还需要删除缓存的方法. 现在看看removeData的代码
Java代码
jQuery.removeData
removeData: function( elem, name ) {
if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
//同data方法
return;
}
elem = elem == window ?
windowData :
elem; //同data方法
var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ];
if ( name ) { //如果有指定的key
if ( thisCache ) { //如果元素有缓存
delete thisCache[ name ]; //直接delete掉
if ( jQuery.isEmptyObject(thisCache) ) {
//如果thisCache是个空对象, 说明所有缓存的数据都已经被删掉了.
jQuery.removeData( elem );
/*
重新调用一次removeData方法, 删掉缓存系统里已经无用的东西,
防止内存泄露, 注意现在走的是下面else分支.
*/
}
}
} else {
if ( jQuery.support.deleteExpando ) {
//如果支持delete, 见特性检测部分.
delete elem[ jQuery.expando ]; //删掉元素的expando属性
} else if ( elem.removeAttribute ) {
//如果支持removeAttribute
elem.removeAttribute( jQuery.expando );
}
delete cache[ id ]; //全局缓存里删除以这个id为key的对象
}
}
});