您的位置:首页 > Web前端 > JQuery

jQuery源码分析系列:数据缓存

2013-04-23 15:28 417 查看
很多同学在项目中都喜欢将数据存储在HTMLElement属性上,如

<div data="some data">Test</div>
<script>
div.getAttribute('data'); // some data
</script>


给页面中div添加了自定义属性“data”及值“some data”。后续JS代码中使用getAttribute获取。

jQuery从1.2.3开始提供了data/removeData方法用来存储/删除数据。1.6.1代码片段

jQuery.extend({
cache: {},

// Please use with caution
uuid: 0,

...

});


即给jQuery添加了静态字段/方法,有jQuery.cache/jQuery.uuid/jQuery.expando等。下面分别介绍

jQuery.cache 空对象,用来缓存。它的结构较复杂。

jQuery.uuid 自增唯一的数字。

jQuery.expando
字符串,使用Math.random生成,去掉了非数字字符。它作为HTMLElement或JS对象的属性名。

expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),


jQuery.noData
JS对象,对于指定的HTMLElement禁用data方法。如embed、applet。

jQuery.hasData
用来判断HTMLElement或JS对象是否具有数据。返回true或false。即如果调用了jQuery.data方法添加了属性,则返回true。

<div>aa</div>
<script>
var div = document.getElementsByTagName('div')[0];
$.hasData(div); // false
$.data(div, 'name','jack');
$.hasData(div); // true
</script>


jQuery.acceptData 用来判断该元素是否能接受数据,返回true或false。在jQuery.data中使用。

jQuery.data 这是提供给客户端程序员使用的方法,它同时是setter/getter。

1,传一个参数,返回附加在指定元素的所有数据,即thisCachejQuery.data(el);
// thisCache

2,传二个参数,返回指定的属性值jQuery.data(el, 'name');

3,传三个参数,设置属性及属性值jQuery.data(el, 'name', 'jack');jQuery.data(el, 'uu',
{});
4,传四个参数,第四个参数pvt仅提供给jQuery库自身使用。即jQuery._data方法中传true。因为jQuery的事件模块严重依赖于jQuery.data,为避免人为的不小心重写在这个版本中加入的。

jQuery.removeData
删除数据。

上面是jQuery数据缓存模块的整体概述,下面详细说下jQuery.data方法。jQuery.data为两种对象提供缓存:JS对象和HTMLElement

// 为JS对象提供缓存
var myObj = {};
$.data(myObj, 'name', 'jack');
$.data(myObj, 'name'); // jack

// 为HTMLElement提供缓存
<div id="xx"></div>
<script>
var el = document.getElementById('xx');
$.data(el, 'name', 'jack');
$.data(el, 'name'); // jack
</script>


内部实现上也是有区别的,

1,为JS对象提供缓存时,直接将数据保存在JS对象上。cache为JS对象。此时会偷偷的给JS对象添加个属性(类似于jQuery16101803968874529044),属性值也是个JS对象。举例说明

var myObj = {};
$.data(myObj, 'name', 'jack');
console.log(myObj);


myObj的结构如下

myObj = {
jQuery16101803968874529044 : {
name : 'jack'
}
}


“jQuery16101803968874529044”这个字符串在data内部命名为id(注意并非HTMLElement元素的id),它实际就是jQuery.expando。上面已经提到它是在jQuery.js引入到页面后随机生成的。

2,为HTMLElement提供缓存时,却不会直接保存在HTMLElement上。而是保存在jQuery.cache上。cache为jQuery.cache。此时先给HTMLElement添加属性(类似于jQuery16101803968874529044),属性值为数字(1,2,3递增)。即只将一些数字保存在了HTMLElement上,不会直接将数据置入。这是因为IE老版本中可能会存在内存泄露危险。而HTMLElement如何与jQuery.cache建立联系呢? 还是id。刚刚提到属性值数字就是id。举例说明

<div id="xx"></div>
<script>
var el = document.getElementById('xx');
$.data(el, 'name', 'jack');
console.log(el[jQuery.expando]); // 1
console.log(jQuery.cache); // {1 : {name:'jack'}}
</script>


el 上添加了属性jQuery.expando,值为id,这个id是从1开始递增的。而id又作为jQuery.cache的属性(key)。这样就HTMLElement就与jQuery.cache建立了联系。如图



不知注意到没有,jQuery.data还有第四个参数pvt,这个参数只在jQuery._data中使用。

// For internal use only.
_data: function( elem, name, data ) {
return jQuery.data( elem, name, data, true );
},


jQuery._data从命名上就指定它是私有的,使用jQuery的客户端程序员不应该去调用该方法。jQuery的API文档上也不会公开它。

jQuery的数据缓存模块从1.2.3到1.6.1几乎每个版本都在变。jQuery._data的提出就是为了避免客户端程序员覆盖/重写了默写模块。如jQuery事件模块中事件handler等就使用jQuery.data存储,如果重写了该模块。那么事件模块将瘫痪。因此特意添加了pvt参数及jQuery._data方法。

但如果你刻意要破坏,那么还是可以做的。如下

<div id="xx">Test</div>
<script>
$('#xx').click(function(){
alert('click');
});

// 语句1
$.data($('#xx')[0], 'events', '', true);

// 语句2
//$._data($('#xx')[0], 'events', '');
</script>


整个jQuery.data设置(set)数据缓存的过程就是如此,理解的这个。取数据(get)的过程就好理解了。不重复。

用jQuery.extend方法扩展工具函数,jQuery.fn.extend调用jQuery.extend中扩展的方法缓存数据。

数据缓存源码:

/* Start Data cache*/
var rbrace = /^(?:\{.*\}|\[.*\])$/,//花括号
rmultiDash = /([a-z])([A-Z])/g;//驼峰写法,大小写之间会被插入破折号
/**写入*/
//在匹配的DOM元素(elem)上附加一个唯一ID,在$.cache中添加同样的ID属性,该ID属性的值是一个对象,其中存储了key/value的映射

//.data(key,value)用来设置保存数据  .data(key)用来查询数据
jQuery.extend({
//数据存储在$.cache中,关键实现的核心
cache: {},
//整型值,初始为0 调用data接口自动加一  生成新的唯一ID  分配ID用的seed
uuid: 0,
/*    唯一ID附加在$.expando命名的属性上,$.expando是动态生成的,避免命名冲突  移除非数字编号的
为了区分不同的jQuery实例存储的数据,前缀 + 版本号 + 随机数作为    key*/
expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),

//以下元素没有data 除了Flash之外的object
noData: {
"embed": true,//用于播放一个多媒体对象 ,flash 音频 视频
"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",//用于向页面添加多媒体对象 flash 音频 视频
"applet": true//object用于IE浏览器   embed元素用于非IE浏览器
},

//判断一个元素事发后有与之关联的数据(通过jQuery.data设置) 用在事件处理中
hasData: function( elem ) {
//如果是DOM,就从jQuery.cache中读取,关联的jQuery.cache和DOM元素的id存储在jQueyr.expando中
//如果非DOM 直接从elem上取,jQuery.expando属性中有数据
//elem的属性jQuery.expando 要么值是id  要么存储数据
elem = elem.nodeType ? jQuery.cache[elem[jQuery.expando]]:elem[jQuery.expando];
return !!elem && !isEmptyDataObject(elem);
},
/*
工具函数:
jQuery.data(elem,key,value)        在指定元素上存储、添加任意数据 处理了循环引用和内存泄露问题
jQuery.data(elem,key)            返回指定元素上name指定的值
jQuery.data(elem)                返回全部数据

pvt私有的  是否是内部使用的独立对象  pvt为true用于事件处理

myObj = {  js缓存直接绑定在js对象上
jQuery16101803968874529044 : {
name : 'jack'
}
}
示例:
<div id="xx"></div>
<script>  html是放在jquery.cache上面的
var el = document.getElementById('xx');
$.data(el, 'name', 'jack');
console.log(el[jQuery.expando]); // 1
console.log(jQuery.cache); // { 1 : { name:'jack'}}
</script>

HTMLElement -> jQuery.expando ->ID ->jQuery.cache

*/
/*
data部分的代码明确区分了JS对象 和 DOM对象的保存,为了解决部分浏览器的内存泄露问题。当DOM和JS对象之间出现循环引用时,GC就无法正确处理。

*/
data: function( elem, name, data, pvt /* Internal Use Only */ ) {
//如果属于noData中定义的元素,是否可以附加数据,不可以直接返回
if(!jQuery.acceptData(elem)){
return;
}

var internalKey = jQuery.expando,//??
getByName = typeof name === "string",
thisCache,
//区分处理DOM元素和JS对象 IE不能垃圾回收对象跨DOM和JS 因为IE6-7不能垃圾回收对象跨DOM对象和JS对象进行的引用属性
isNode = elem.nodeType,

//如果是DOM 使用全局jQuery.cache
//如果是JS对象,直接附加在对象上
cache = isNode ? jQuery.cache : elem,

//如果JS对象的cache已经存在,需要为JS对象定义一个ID
//如果DOM元素,直接取elem[jQuery.expando] 返回id 为递增的
//如果是JS对象,且JS对象的属性jQuery.expando存在,返回jQuery.expando
//var internalKey = jQuery.expando
id = isNode ? elem[jQuery.expando] : elem[jQuery.expando] && jQuery.expando;
//id不存在  id存在但是internalKey=jQuery.expando不存在
//data未定义,说明当前调用是查询数据,但是对象没有任何数据  直接返回
if((!id || (pvt && id && !cache[id][internalKey])) && getByName && data === undefined){
return;
}
//HTMLElement -> jQuery.expando -> id -> jQuery.cache
//id不存在生成一个  设置id
if(!id){
if(isNode){
//HTMLElement添加属性(类似于jQuery16101803968874529044),属性值为数字 1 2 3 4
//用uuid递增分配唯一ID,只有DOM元素需要。需要存在全局cache中
elem[jQuery.expando] = id = ++ jQuery.uuid;
}else{
//避免与其他属性冲突 “jQuery16101803968874529044”这个字符串在data内部命名为id
id = jQuery.expando;
}
}

//数据存储在一个映射对象中   清空原有的值
if(!cache[id]){
cache[id] = {};//初始化存储对象
if(!isNode){
cache[id].toJSON = jQuery.noop;
}
}
//用extend扩展cache,增加一个属性,用来保存数据。
//data接口接受对象和函数 浅拷贝
if(typeof name === "object" || typeof name === "function"){
if(pvt){
//id: 1  2  3  4  5 ...
cache[id][internalKey] = jQuery.extend(cache[id][internalKey],name);
}else{
cache[id] = jQuery.extend(cache[id],name);
}
}
//存储了所有的数据的映射对象
thisCache = cache[id];

//避免key冲突
if ( pvt ) {
if ( !thisCache[ internalKey ] ) {
thisCache[ internalKey ] = {};//设空对象
}
thisCache = thisCache[ internalKey ];
}

if ( data !== undefined ) {
thisCache[ jQuery.camelCase( name ) ] = data;
}
//忽略
if ( name === "events" && !thisCache[name] ) {
return thisCache[ internalKey ] && thisCache[ internalKey ].events;
}
//如果name是字符串,返回data  不是 返回整个存储对象
return getByName ? thisCache[ jQuery.camelCase( name ) ] : thisCache;
},
//删除数据
removeData: function( elem, name, pvt /* Internal Use Only */ ) {
//如果元素不能附加数据
if ( !jQuery.acceptData( elem ) ) {
return;
}
//internalKey定义唯一ID
var internalKey = jQuery.expando,
//是DOM对象
isNode = elem.nodeType,

//DOM对象用cache全局缓存   JS对象存在elem中
cache = isNode ? jQuery.cache : elem,
// 如果elem的jQuery.expando已经有值了,就重用 减少ID数
id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
//缓存中没有这个元素  直接返回
if ( !cache[ id ] ) {
return;
}
//部分浏览器不支持deleteExpando ,在jQuery.support中检查过这个浏览器特性 失败的话 设null
if(jQuery.support.deleteExpando || cache != window){
delete cache[id];
}else{
cache[id] = null;
}

var internalCache = cache[id][internalKey];
//如果还有数据  就清空一次在设置 增加性能
if(internalCache){
cache[id] = {};
cache[id][internalKey] = internalCache;
//没有任何数据了
}else if(isNode){
//如果支持delete 就删除  IE使用reomveAttribute
if(jQuery.support.deleteExpando){
delete elem[jQuery.expando];
}else if(elem.reomveAttribute){
elem.reomveAttribute(jQuery.expando);
}else{
elem[jQuery.expando] = null;
}
}
},
_data: function( elem, name, data ) {
return jQuery.data(elem,name,data,true);
},

//判断一个元素是否可以附加数据
acceptData: function( elem ) {
if(elem.nodeName){
//embed object applet  以下元素没有data
var match = jQuery.noData[elem.nodeName.toLowerCase()];
if(match){
//getAttribute():查询属性的名字  返回false
return !(match === true || elem.getAttribute("classid") !== match);
}
}
return true;
}
});

jQuery.fn.extend({
//向被选元素添加数据  和读取数据
data: function( key, value ) {
var data = null;//初始化
//用于处理特殊key   key是undefined  则认为取当前jQuery对象中第一个元素的全部数据
if(typeof key === "undefined"){
if(this.length){
data = jQuery.data(this[0]);
//Element
if(this[0].nodeType === 1){
var attr = this[0].attributes,name;
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]);
}
}
}
return data;
//key是对象 则对当前jQuery对象迭代调用$.fn.each
//在每一个匹配的元素上存储数据key
}else if(typeof key === "object" ){
return this.each(function(){
jQuery.data(this,key);
}
}
// 到这里,key是字符串
var parts = key.split(".");
parts[1] = parts[1] ? "." + parts[1] : "";
//如果value为undefined,则   取当前jQuery对象中第一个元素指定名称的数据
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 );
// 读取HTML5的data属性
data = dataAttr( this[0], key, data );
}

return data === undefined && parts[1] ?
this.data( parts[0] ) :
data;

// key是字符串,value不是undefined,则存储
} else {
return this.each(function() {
var $this = jQuery( this ),
args = [ parts[0], value ];
// 触发事件
$this.triggerHandler( "setData" + parts[1] + "!", args );
jQuery.data( this, key, value );
$this.triggerHandler( "changeData" + parts[1] + "!", args );
});
}
},
//删除数据
removeData: function( key ) {
return this.each(function() {
jQuery.removeData( this, key );
});
}
});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: