一.引子
? ? 大家想想,在未使用jQuery或者其他js框架前,只用原生js的时候,怎么存储数据的呢?在刚结束js的时候,我是将每个跟节点有关的属性都使用setAttribute(name,value)保存在节点上。下次取就可以直接getAttribute(name),一个节点使用这种方式保存很多数据,效率肯定不高的。在有时候要对节点保存大量的数据的时候,还有没更好的办法来存储数据呢?下面来看看jQuery是如何做的。
?
二.原理
? ? jQuery数据的存储原理是:
1)定义了一个对象$.cache 保存所有数据
2)对每一个存储数据的DOM节点都对应一个数字index,这个DOM节点下的所有值都存储在$.cache(index)对象中(这么看是不是觉得$.cache是一个数组? 其实js中数组和对象很像,数组也是对象,对象就是一组属性的集合)。
3)对每一个存储数据的DOM节点都生成一个唯一的index。这个index值保存在节点的expando属性中。
4)expando是什么?就是每一个jQuery框架加载的时候内部生成的一个随机序列。这个序列一个jQuery加载完只有唯一一个。
?
让图片来更清楚的描述:

调用$(“#test”).data(“name”)时会先找到对象属性jQuery17102199497243038011($.expando)的值(当前为1),这个值就1就是上面说的index。对象所有存储的值都是放在$.cache[“1”]对象中。
下面代码可验证:
$("#test").data("abc", "def");
var cacheIndex = document.getElementById("test")[$.expando]; ?//获取index
//$.expando在1.2版本里访问不到,不能
var obj = $.cache[cacheIndex];
alert(obj["abc"]); ?//def 注:这是jQuery1.6 前取值方式
//注: 在jquery1.7版本中对存储值有所改变。需要obj[“data”]["abc"]才能取到。也就是说$(“#test”).data(“abc”,”def”)不是存在上述obj对象中,而是存在obj[“data”]对象中(多了一层data对象,变得更深了)。
三.源码
下面来看代码是如何做到的:
jQuery.fn.extend({
//扩展jQuery的对象方法data
data: function( key, value ) {
var parts, attr, name,data = null;
//如果key没有,执行的操作
if ( typeof key === "undefined" ) {
if ( this.length ) {
/**获取对象数组第一个元素的数据缓存cache
* (该对象包含当前元素的所有存储的数据)
*/
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;
} else if ( typeof key === "object" ) {
return this.each(function() {
jQuery.data( this, key );
});
}
parts = key.split(".");
parts[1] = parts[1] ? "." + parts[1] : "";
//.data(name)格式,取值
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;
//.data(name, value)格式,设置值
} 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 );
});
}
}
});
?
下面是静态方法:
?
jQuery.extend({
/** jQuery静态方法,算是data的核心了
* jQuery很多都是这种模式,在静态方法里定义一个核心的处理方法
* 在对象方法中定义用户操作的接口,就是API。
* 如事件:$("#id").click(),$("xx").hover()等等。
* 但最终处理都是调$("#id").on() 到最后是$.event.add()方法
*/
data: function( elem, name, data, pvt /* Internal Use Only */ ) {
//判断当前节点能不能存储数据
if ( !jQuery.acceptData( elem ) ) {
return;
}
var privateCache, thisCache, ret,
internalKey = jQuery.expando,
getByName = typeof name === "string",
/**判断元素是否是DOM对象,如果是DOM对象才使用全局cache,普通js对象,直接增加属性即可.
* DOM对象可以直接增加属性,为什么这个非得用全局对象呢?
* 这主要是解决IE6-7浏览器垃圾回收对于js创建的DOM对象的属性不能回收
* 验证方法:(使用sIEve 可看到有内存泄露)
* function A() {
* var a = document.createElement("div");
* a["b"] = function(){};
* document.getElementById("test").appendChild(a);
* a.parentNode.removeChild(a);
* }
* A();
*/
isNode = elem.nodeType,
cache = isNode ? jQuery.cache : elem,
//获取index
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 ) {
return;
}
if ( !id ) {
//为每一个存储数据在全局cache中的DOM节点分配一个唯一index
if ( isNode ) {
elem[ internalKey ] = id = ++jQuery.uuid;
} else {
id = internalKey;
}
}
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;
}
}
//如果name是对象或函数,继承里面所有方法.注意:这边为什么判断pvt ?
if ( typeof name === "object" || typeof name === "function" ) {
if ( pvt ) {
cache[ id ] = jQuery.extend( cache[ id ], name );
} else {
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
}
}
privateCache = thisCache = cache[ id ];
//要知道pvt是jQuery自己内部使用的,将保存用户和jQuery内部数据的保存分开了。
//将我们设置的name/value值
//放在了cache[id].data对象中,而jQuery内部数据直接放在了cache[id]中
//好处不言而喻,将用户和jQuery框架的数据分开保存,避免了名称冲突
//1.7版本之后才区分开.上面例子已经说了
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.
if ( isEvents && !thisCache[ name ] ) {
return privateCache.events;
}
if ( getByName ) {
ret = thisCache[ name ];
if ( ret == null ) {
ret = thisCache[ jQuery.camelCase( name ) ];
}
} else {
ret = thisCache;
}
return ret;
}
});
?
本章结束,有不对和不准确的地方望大家指正。有疑问欢迎留言。
?