深入学习jQuery中的data()
data有什么作用?
在我们平时js编码过程中,我们经常会向DOM元素中添加各种自定义属性,这样有一个弊端。
1、假设我们在DOM元素中添加了一个属性,这个属性指向了某个js对象。dom1.ele=jsObj
2、当这个js对象发挥完作用后,我们已经用不到他了。这时候按理说应该把这个js变量清空,释放内存。大家都知道,如果一个js对象不存在任何外在引用的话,解释器会自动将其在内存中删除,这也是javascript相对于c++等手动管理内存的程序的优点。
3、但是这时候问题来了,因为DOM元素引用了这个js对象,尽管这个js对象已经没有存在的意义了,但是解释器是不会把他删除的。如果想要把其删除,我们可能需要将DOM元素的这个属性设置为null。
4、我们编写了这么多的代码,哪里能把每个js对象是不是被DOM元素引用了都记住啊?
5、而且,假如DOM元素与js对象之间相互循环引用,根本就无法删除!这就是内存泄漏
6、所以,为了避免这种情况的发生,我们要尽量避免引用数据(这里的引用数据可以说是javascript对象)直接依附在DOM对象上。
7、data就是用来搞定以上问题的方法。
data是如何搞定以上问题的?
首先来说一说jQuery中Data实现的大体思路:
1、首先我们创建一个数据缓存池,这个缓存池专门用来存储 向DOM对象或者jQuery对象附加的额外数据。
2、当我们要向DOM对象或者jQuery对象附加额外数据的时候,我们附加的数据其实是保存于这个缓存池中
3、DOM对象或者jQuery对象生成一个额外属性,这个属性保存了附加数据在缓存池中的‘门牌号'(位置或者索引)
4、当我们访问DOM对象或者jQuery对象的附加数据时,实际上是先取得其附加数据的门牌号,然后找到缓存池中对应门牌号的数据,进行操作。
大体思路讲完,那么来分析一下具体思路:
在jQuery中,有一个Data构造函数,每当运行这个构造函数时,就会生成一个实例。
jQuery默认会自动生成两个Data实例:
vardataPriv=newData() jQuery私有的,我们尽量不要对这个实例进行操作。
vardataUser=newData() 这个就是服务于用户了,我们使用data()方法都是对这个实例进行操作。
所有的Data实例都有以下属性:
expando: 值为字符串类型,每个Data实例的expando属性的值都不相同,用来区分不同的Data实例,类似于id的作用,expando的值就是上文中的额外属性。
uid: 这就是上文中的门牌号,初始为1,随着不同对象的附加数据的加入,自增长。
cache:一个对象{},这就是缓存池了。
来个实例:
$(document.body).data('aaa','value-aaa')
console.dir(document.body)
body对象有一个名为jquer210023......的额外属性,
这个属性的名称就是dataUser的expando的值
这个属性的值就是门牌号。
总结:data实际上就是对js对象或者DOM对象的额外属性做了一个集中的管理。对于那些不会产生内存泄漏的额外数据,我们也可以直接向js对象或者DOM对象附加。
好,理清楚上面的关系后,我们再来看一下源码:
define([
"../core",
"../var/rnotwhite",
"./accepts"
],function(jQuery,rnotwhite){
functionData(){
//Support:Android<4,
//OldWebKitdoesnothaveObject.preventExtensions/freezemethod,
//returnnewemptyobjectinsteadwithno[[set]]accessor
Object.defineProperty(this.cache={},0,{
get:function(){
return{};
}
});
//jQuery.expando="jQuery"+(version+Math.random()).replace(/\D/g,"")expando是一个jQuery的唯一标示
//格式是:'jQuery\\d*'也就是'jQuery'+多个数字。这里为啥要搞得这么麻烦呢?
//应因为我们可能会创建多个Data对象,为了保证每个Data对象的expando属性的值不相等,所以这么搞
this.expando=jQuery.expando+Math.random();
}
Data.uid=1;//Data函数的属性,'静态属性'
Data.accepts=jQuery.acceptData;
Data.prototype={
key:function(owner){
//Wecanacceptdatafornon-elementnodesinmodernbrowsers,
//butweshouldnot,see#8335.
//Alwaysreturnthekeyforafrozenobject.
//若owner在该缓存池中存在对应的缓存对象,则返回混存对象的key(是一个数字),
//若owner在该缓存池中不存在对应的缓存对象,则在缓存池中为其创建一个缓存对象,并返回该缓存对象的key
if(!Data.accepts(owner)){
return0;
}
vardescriptor={},
//Checkiftheownerobjectalreadyhasacachekey
//检查owner对象在该缓存池中是否存在缓存
unlock=owner[this.expando];//是一个数字,用来作为缓存池中缓存对象的key
//Ifnot,createone
//如果没有,则创建一个
if(!unlock){
unlock=Data.uid++;
//Secureitinanon-enumerable,non-writableproperty
//给owner附加一个属性owner[this.expando]=unlock,并且该属性不能被枚举,
try{
descriptor[this.expando]={value:unlock};
Object.defineProperties(owner,descriptor);
//Support:Android<4
//Fallbacktoalesssecuredefinition
}catch(e){
descriptor[this.expando]=unlock;
jQuery.extend(owner,descriptor);
}
}
//Ensurethecacheobject
//确保owner对应的缓存对象已存在
if(!this.cache[unlock]){
this.cache[unlock]={};
}
//返回unlock
returnunlock;
},
set:function(owner,data,value){
//设置owner对应的缓存对象
varprop,
//Theremaybeanunlockassignedtothisnode,
//ifthereisnoentryforthis"owner",createoneinline
//andsettheunlockasthoughanownerentryhadalwaysexisted
unlock=this.key(owner),//获取owner的对应的缓存对象在缓存池中的key(这里的key,是键值对中的键的意思)
cache=this.cache[unlock];//获取owner所对应的缓存对象
//Handle:[owner,key,value]args
//根据传入参数的个数以及类型实现重载
if(typeofdata==="string"){
cache[data]=value;
//Handle:[owner,{properties}]args
}else{
//Freshassignmentsbyobjectareshallowcopied
if(jQuery.isEmptyObject(cache)){
jQuery.extend(this.cache[unlock],data);
//Otherwise,copythepropertiesone-by-onetothecacheobject
}else{
for(propindata){
cache[prop]=data[prop];
}
}
}
//返回缓存对象
returncache;
},
get:function(owner,key){
//获取owner对象的名为key的属性值
//owner:是一个对象(可以是jQuery对象也可以是DOM对象)key:属性名
//Eitheravalidcacheisfound,orwillbecreated.
//Newcacheswillbecreatedandtheunlockreturned,
//allowingdirectaccesstothenewlycreated
//emptydataobject.Avalidownerobjectmustbeprovided.
varcache=this.cache[this.key(owner)];//owner的缓存对象
returnkey===undefined?cache:cache[key];//没指定key的话就返回整个缓存对象,若指定了key则返回在该缓存对象的key属性的值
},
access:function(owner,key,value){
varstored;
//Incaseswhereeither:
//
//1.Nokeywasspecified没有指定key
//2.Astringkeywasspecified,butnovalueprovided指定了字符串格式的key,但没有指定value
//
//Takethe"read"pathandallowthegetmethodtodetermine
//whichvaluetoreturn,respectivelyeither:
//
//1.Theentirecacheobject整个缓存对象
//2.Thedatastoredatthekey缓存对象中某个键的值
//
if(key===undefined||//没有指定key或者指定了字符串格式的key,但没有指定value
((key&&typeofkey==="string")&&value===undefined)){
//没有指定key:获取整个缓存对象
//指定了字符串格式的key,但没有指定value:获取缓存对象中key的值
stored=this.get(owner,key);
returnstored!==undefined?
stored:this.get(owner,jQuery.camelCase(key));
}
//[*]Whenthekeyisnotastring,orbothakeyandvalue
//arespecified,setorextend(existingobjects)witheither:
//当key不是一个字符串,或者key和value都指定了,就会根据情况进行设置或者扩展
//
//1.Anobjectofproperties
//2.Akeyandvalue
//
this.set(owner,key,value);
//Sincethe"set"pathcanhavetwopossibleentrypoints
//returntheexpecteddatabasedonwhichpathwastaken[*]
returnvalue!==undefined?value:key;
},
remove:function(owner,key){
//清空owner对应的缓存对象,或者移除缓存对象中的某个键值对
vari,name,camel,
unlock=this.key(owner),
cache=this.cache[unlock];
//如果没有指定key,则清空缓存对象
if(key===undefined){
this.cache[unlock]={};
}else{
//Supportarrayorspaceseparatedstringofkeys
if(jQuery.isArray(key)){
//If"name"isanarrayofkeys...
//Whendataisinitiallycreated,via("key","val")signature,
//keyswillbeconvertedtocamelCase.
//Sincethereisnowaytotell_how_akeywasadded,remove
//bothplainkeyandcamelCasekey.#12786
//Thiswillonlypenalizethearrayargumentpath.
name=key.concat(key.map(jQuery.camelCase));
}else{
camel=jQuery.camelCase(key);
//Trythestringasakeybeforeanymanipulation
if(keyincache){
name=[key,camel];
}else{
//Ifakeywiththespacesexists,useit.
//Otherwise,createanarraybymatchingnon-whitespace
name=camel;
name=nameincache?
[name]:(name.match(rnotwhite)||[]);
}
}
i=name.length;
while(i--){
deletecache[name[i]];
}
}
},
hasData:function(owner){
//检查owner在该缓存池中是否存在缓存对象
return!jQuery.isEmptyObject(
this.cache[owner[this.expando]]||{}
);
},
discard:function(owner){
if(owner[this.expando]){
deletethis.cache[owner[this.expando]];
}
}
};
returnData;
});
可能会有同学问道:如果我想对dataPriv进行操作该如何?
请看源码:
jQuery.extend({
hasData:function(elem){
returndataUser.hasData(elem)||dataPriv.hasData(elem);
},
data:function(elem,name,data){
returndataUser.access(elem,name,data);
},
removeData:function(elem,name){
dataUser.remove(elem,name);
},
//TODO:Nowthatallcallsto_dataand_removeDatahavebeenreplaced
//withdirectcallstodataPrivmethods,thesecanbedeprecated.
_data:function(elem,name,data){
returndataPriv.access(elem,name,data);
},
_removeData:function(elem,name){
dataPriv.remove(elem,name);
}
});
通过源码,我们可以看出:
jQuery.data()jQuery.remove()都是对dataUser进行操作,而jQuery._data()jQuery._remove()都是对dataPriv进行操作。
理解jQuery.data(ele,name,data)与jQuery().data(key,value)的不同。
通过上面的源码,我们可以看到jQuery.data(ele,name,data)是对ele元素附加数据。
而jQuery().data(key,value)则会为jQuery对象中的所有DOM对象分别附加数据
来看源码(删减了部分):
jQuery.fn.extend({
data:function(key,value){
vari,name,data,
elem=this[0],
attrs=elem&&elem.attributes;returnaccess(this,function(value){
vardata,
camelKey=jQuery.camelCase(key);
//从这里可以看出,为jQuery对象中的每个DOM元素分别附加数据
this.each(function(){
//First,attempttostoreacopyorreferenceofany
//datathatmight'vebeenstorewithacamelCasedkey.
vardata=dataUser.get(this,camelKey);
//ForHTML5data-*attributeinterop,wehaveto
//storepropertynameswithdashesinacamelCaseform.
//Thismightnotapplytoallproperties...*
dataUser.set(this,camelKey,value);
//*...Inthecaseofpropertiesthatmight_actually_
//havedashes,weneedtoalsostoreacopyofthat
//unchangedproperty.
if(key.indexOf("-")!==-1&&data!==undefined){
dataUser.set(this,key,value);
}
});
},null,value,arguments.length>1,null,true);
},
removeData:function(key){
returnthis.each(function(){
dataUser.remove(this,key);
});
}
});
上文中的所有源码:为jQuery.1.12
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者动作能带来一定的帮助,如果有疑问大家可以留言交流。