jQuery数据缓存$.data 的使用以及源码解析
jQuery数据缓存$.data的使用以及源码解析
实现原理
总体结构
$.data(elem,name,data),$.data(elem,name)
.data(key,value),.data(key)
$.removeData(elem,name),.removeData(key)
$.hasData(elem)
一、实现原理:
对于DOM元素,通过分配一个唯一的关联id把DOM元素和该DOM元素的数据缓存对象关联起来,关联id被附加到以jQuery.expando的值命名的属性上,数据存储在全局缓存对象jQuery.cache中。在读取、设置、移除数据时,将通过关联id从全局缓存对象jQuery.cache中找到关联的数据缓存对象,然后在数据缓存对象上执行读取、设置、移除操作。
对于Javascript对象,数据则直接存储在该Javascript对象的属性jQuery.expando上。在读取、设置、移除数据时,实际上是对Javascript对象的数据缓存对象执行读取、设置、移除操作。
为了避免jQuery内部使用的数据和用户自定义的数据发生冲突,数据缓存模块把内部数据存储在数据缓存对象上,把自定义数据存储在数据缓存对象的属性data上。
二、总体结构:
//数据缓存Data
jQuery.extend({
//全局缓存对象
cache:{},
//唯一id种子
uuid:0,
//页面中每个jQuery副本的唯一标识
expando:"jQuery"+(jQuery.fn.jquery+Math.random()).replace(/\D/g,""),
//是否有关联的数据
hasData:function(){},
//设置、读取自定数据或内部数据
data:function(elem,name,data,pvt){},
//移除自定义数据或内部数据
removeData:function(elem,name,pvt){},
//设置、读取内部数据
_data:function(elem,name,data){},
//是否可以设置数据
acceptData:function(elem){}
});
jQuery.fn.extend({
//设置、读取自定义数据,解析HTML5属性data-
data:function(key,value){},
//移除自定义数据
removeData:function(key){}
});
//解析HTML5属性data-
functiondataAttr(elem,key,data){}
//检查数据缓存对象是否为空
functionisEmptyDataObject(obj){}
jQuery.extend({
//清空数据缓存对象
cleanData:function(elems){}
});
三、$.data(elem,name,data),$.data(elem,name)
$.data(elem,name,data)的使用方法:
如果传入参数name,data,则设置任意类型的数据
<!doctypehtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>jQuery.datademo</title>
<style>
div{
color:blue;
}
span{
color:red;
}
</style>
<scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<div>
Thevaluesstoredwere
<span></span>
and
<span></span>
</div>
<script>
vardiv=$("div")[0];
jQuery.data(div,"test",{
first:16,
last:"pizza!"
});
$("span:first").text(jQuery.data(div,"test").first);
$("span:last").text(jQuery.data(div,"test").last);
</script>
</body>
</html>
$.data(elem,name)的使用方法:
如果传入key,未传入参数data,则读取并返回指定名称的数据
<!doctypehtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>jQuery.datademo</title>
<style>
div{
margin:5px;
background:yellow;
}
button{
margin:5px;
font-size:14px;
}
p{
margin:5px;
color:blue;
}
span{
color:red;
}
</style>
<scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<div>Adiv</div>
<button>Get"blah"fromthediv</button>
<button>Set"blah"to"hello"</button>
<button>Set"blah"to86</button>
<button>Remove"blah"fromthediv</button>
<p>The"blah"valueofthisdivis<span>?</span></p>
<script>
$("button").click(function(){
varvalue,
div=$("div")[0];
switch($("button").index(this)){
case0:
value=jQuery.data(div,"blah");
break;
case1:
jQuery.data(div,"blah","hello");
value="Stored!";
break;
case2:
jQuery.data(div,"blah",86);
value="Stored!";
break;
case3:
jQuery.removeData(div,"blah");
value="Removed!";
break;
}
$("span").text(""+value);
});
</script>
</body>
</html>
$.data(elem,name,data),$.data(elem,name)源码解析:
jQuery.extend({
//1.定义jQuery.data(elem,name,data,pvt)
data:function(elem,name,data,pvt/*InternalUseOnly*/){
//2.检查是否可以设置数据
if(!jQuery.acceptData(elem)){
return;//如果参数elem不支持设置数据,则立即返回
}
//3定义局部变量
varprivateCache,thisCache,ret,
internalKey=jQuery.expando,
getByName=typeofname==="string",
//WehavetohandleDOMnodesandJSobjectsdifferentlybecauseIE6-7
//can'tGCobjectreferencesproperlyacrosstheDOM-JSboundary
isNode=elem.nodeType,//elem是否是DOM元素
//OnlyDOMnodesneedtheglobaljQuerycache;JSobjectdatais
//attacheddirectlytotheobjectsoGCcanoccurautomatically
cache=isNode?jQuery.cache:elem,//如果是DOM元素,为了避免javascript和DOM元素之间循环引用导致的浏览器(IE6/7)垃圾回收机制不起作用,要把数据存储在全局缓存对象jQuery.cache中;对于javascript对象,来及回收机制能够自动发生,不会有内存泄露的问题,因此数据可以查收存储在javascript对象上
//OnlydefininganIDforJSobjectsifitscachealreadyexistsallows
//thecodetoshortcutonthesamepathasaDOMnodewithnocache
id=isNode?elem[internalKey]:elem[internalKey]&&internalKey,
isEvents=name==="events";
//Avoiddoinganymoreworkthanweneedtowhentryingtogetdataonan
//objectthathasnodataatall
//4.如果是读取数据,但没有数据,则返回
if((!id||!cache[id]||(!isEvents&&!pvt&&!cache[id].data))&&getByName&&data===undefined){
return;
//getByName&&data===undefined如果name是字符串,data是undefined,说明是在读取数据
//!id||!cache[id]||(!isEvents&&!pvt&&!cache[id].data如果关联id不存在,说明没有数据;如果cache[id]不存在,也说明没有数据;如果是读取自动以数据,但cache[id].data不存在,说明没有自定义数据
}
//5.如果关联id不存在,则分配一个
if(!id){
//OnlyDOMnodesneedanewuniqueIDforeachelementsincetheirdata
//endsupintheglobalcache
if(isNode){
elem[internalKey]=id=++jQuery.uuid;//对于DOM元素,jQuery.uuid会自动加1,并附加到DOM元素上
}else{
id=internalKey;//对于javascript对象,关联id就是jQuery.expando
}
}
//6.如果数据缓存对象不存在,则初始化为空对象{}
if(!cache[id]){
cache[id]={};
//AvoidsexposingjQuerymetadataonplainJSobjectswhentheobject
//isserializedusingJSON.stringify
if(!isNode){
cache[id].toJSON=jQuery.noop;//对于javascript对象,设置方法toJSON为空函数,以避免在执行JSON.stringify()时暴露缓存数据。如果一个对象定义了方法toJSON(),JSON.stringify()在序列化该对象时会调用这个方法来生成该对象的JSON元素
}
}
//AnobjectcanbepassedtojQuery.datainsteadofakey/valuepair;thisgets
//shallowcopiedoverontotheexistingcache
//7.如果参数name是对象或函数,则批量设置数据
if(typeofname==="object"||typeofname==="function"){
if(pvt){
cache[id]=jQuery.extend(cache[id],name);//对于内部数据,把参数name中的属性合并到cache[id]中
}else{
cache[id].data=jQuery.extend(cache[id].data,name);//对于自定义数据,把参数name中的属性合并到cache[id].data中
}
}
//8.如果参数data不是undefined,则设置单个数据
privateCache=thisCache=cache[id];
//jQuerydata()isstoredinaseparateobjectinsidetheobject'sinternaldata
//cacheinordertoavoidkeycollisionsbetweeninternaldataanduser-defined
//data.
if(!pvt){
if(!thisCache.data){
thisCache.data={};
}
thisCache=thisCache.data;
}
if(data!==undefined){
thisCache[jQuery.camelCase(name)]=data;
}
//UsersshouldnotattempttoinspecttheinternaleventsobjectusingjQuery.data,
//itisundocumentedandsubjecttochange.Butdoesanyonelisten?No.
//9.特殊处理events
if(isEvents&&!thisCache[name]){//如果参数name是字符串"events",并且未设置过自定义数据"events",则返回事件婚车对象,在其中存储了事件监听函数。
returnprivateCache.events;
}
//Checkforbothconverted-to-camelandnon-converteddatapropertynames
//Ifadatapropertywasspecified
//10.如果参数name是字符串,则读取单个数据
if(getByName){
//FirstTrytofindas-ispropertydata
ret=thisCache[name];//先尝试读取参数name对应的数据
//Testfornull|undefinedpropertydata
if(ret==null){//如果未取到,则把参数name转换为驼峰式再次尝试读取对应的数据
//TrytofindthecamelCasedproperty
ret=thisCache[jQuery.camelCase(name)];
}
}else{//11.如果未传入参数name,data,则返回数据缓存对象
ret=thisCache;
}
returnret;
},
//Forinternaluseonly.
_data:function(elem,name,data){
returnjQuery.data(elem,name,data,true);
},
});
四、.data(key,value),.data(key)
使用方法:
$("body").data("foo",52);//传入key,value
$("body").data("bar",{myType:"test",count:40});//传入key,value
$("body").data({baz:[1,2,3]});//传入key,value
$("body").data("foo");//52//传入key
$("body").data();//未传入参数
HTML5dataattriubutes:
<divdata-role="page"data-last-value="43"data-hidden="true"data-options='{"name":"John"}'></div>
$("div").data("role")==="page";
$("div").data("lastValue")===43;
$("div").data("hidden")===true;
$("div").data("options").name==="John";
.data(key,value),.data(key)源码解析
jQuery.fn.extend({
//1.定义.data(key,value)
data:function(key,value){
varparts,attr,name,
data=null;
//2.未传入参数的情况
if(typeofkey==="undefined"){
if(this.length){//如果参数key是undefined,即参数格式是.data(),则调用方法jQuery.data(elem,name,data,pvt)获取第一个匹配元素关联的自定义数据缓存对象,并返回。
data=jQuery.data(this[0]);
if(this[0].nodeType===1&&!jQuery._data(this[0],"parsedAttrs")){
attr=this[0].attributes;
for(vari=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);
}
}
returndata;
//3.参数key是对象的情况,即参数格式是.data(key),则遍历匹配元素集合,为每个匹配元素调用方法jQuery.data(elem,name,data,pvt)批量设置数据
}elseif(typeofkey==="object"){
returnthis.each(function(){
jQuery.data(this,key);
});
}
//4.只传入参数key的情况如果只传入参数key,即参数格式是.data(key),则返回第一个匹配元素的指定名称数据
parts=key.split(".");
parts[1]=parts[1]?"."+parts[1]:"";
if(value===undefined){
data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);
//Trytofetchanyinternallystoreddatafirst
if(data===undefined&&this.length){
data=jQuery.data(this[0],key);
data=dataAttr(this[0],key,data);
}
returndata===undefined&&parts[1]?
this.data(parts[0]):
data;
//5.传入参数key和value的情况即参数格式是.data(key,value),则为每个匹配元素设置任意类型的数据,并触发自定义事件setData,changeData
}else{
returnthis.each(function(){
varself=jQuery(this),
args=[parts[0],value];
self.triggerHandler("setData"+parts[1]+"!",args);
jQuery.data(this,key,value);
self.triggerHandler("changeData"+parts[1]+"!",args);
});
}
},
removeData:function(key){
returnthis.each(function(){
jQuery.removeData(this,key);
});
}
});
//6.函数dataAttr(elem,key,data)解析HTML5属性data-
functiondataAttr(elem,key,data){
//Ifnothingwasfoundinternally,trytofetchany
//datafromtheHTML5data-*attribute
//只有参数data为undefined时,才会解析HTML5属性data-
if(data===undefined&&elem.nodeType===1){
varname="data-"+key.replace(rmultiDash,"-$1").toLowerCase();
data=elem.getAttribute(name);
if(typeofdata==="string"){
try{
data=data==="true"?true:
data==="false"?false:
data==="null"?null:
jQuery.isNumeric(data)?parseFloat(data):
rbrace.test(data)?jQuery.parseJSON(data):
data;
}catch(e){}
//Makesurewesetthedatasoitisn'tchangedlater
jQuery.data(elem,key,data);
}else{
data=undefined;
}
}
returndata;
}
五、$.removeData(elem,name),.removeData(key)
使用方法:
<!doctypehtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>jQuery.removeDatademo</title>
<style>
div{
margin:2px;
color:blue;
}
span{
color:red;
}
</style>
<scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<div>value1beforecreation:<span></span></div>
<div>value1aftercreation:<span></span></div>
<div>value1afterremoval:<span></span></div>
<div>value2afterremoval:<span></span></div>
<script>
vardiv=$("div")[0];
$("span:eq(0)").text(""+$("div").data("test1"));//undefined
jQuery.data(div,"test1","VALUE-1");
jQuery.data(div,"test2","VALUE-2");
$("span:eq(1)").text(""+jQuery.data(div,"test1"));//VALUE-1
jQuery.removeData(div,"test1");
$("span:eq(2)").text(""+jQuery.data(div,"test1"));//undefined
$("span:eq(3)").text(""+jQuery.data(div,"test2"));//value2
</script>
</body>
</html>
<!doctypehtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>removeDatademo</title>
<style>
div{
margin:2px;
color:blue;
}
span{
color:red;
}
</style>
<scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<div>value1beforecreation:<span></span></div>
<div>value1aftercreation:<span></span></div>
<div>value1afterremoval:<span></span></div>
<div>value2afterremoval:<span></span></div>
<script>
$("span:eq(0)").text(""+$("div").data("test1"));//undefined
$("div").data("test1","VALUE-1");
$("div").data("test2","VALUE-2");
$("span:eq(1)").text(""+$("div").data("test1"));//VALUE-1
$("div").removeData("test1");
$("span:eq(2)").text(""+$("div").data("test1"));//undefined
$("span:eq(3)").text(""+$("div").data("test2"));//VALUE-2
</script>
</body>
</html>
$.removeData(elem,name),.removeData(key)源码解析:
$.extend({
//jQuery.removeData(elem,name,pvt)用于移除通过jQuery.data()设置的数据
removeData:function(elem,name,pvt/*InternalUseOnly*/){
if(!jQuery.acceptData(elem)){
return;
}
varthisCache,i,l,
//Referencetointernaldatacachekey
internalKey=jQuery.expando,
isNode=elem.nodeType,
//SeejQuery.dataformoreinformation
cache=isNode?jQuery.cache:elem,
//SeejQuery.dataformoreinformation
id=isNode?elem[internalKey]:internalKey;
//Ifthereisalreadynocacheentryforthisobject,thereisno
//purposeincontinuing
if(!cache[id]){
return;
}
//如果传入参数name,则移除一个或多个数据
if(name){
thisCache=pvt?cache[id]:cache[id].data;
if(thisCache){//只有数据缓存对象thisCache存在时,才有必要移除数据
//Supportarrayorspaceseparatedstringnamesfordatakeys
if(!jQuery.isArray(name)){
//trythestringasakeybeforeanymanipulation
if(nameinthisCache){
name=[name];
}else{
//splitthecamelcasedversionbyspacesunlessakeywiththespacesexists
name=jQuery.camelCase(name);
if(nameinthisCache){
name=[name];
}else{
name=name.split("");
}
}
}
//遍历参数name中的数据名,用运算符delete逐个从数据缓存对象thisCache中移除
for(i=0,l=name.length;i<l;i++){
deletethisCache[name[i]];
}
//Ifthereisnodataleftinthecache,wewanttocontinue
//andletthecacheobjectitselfgetdestroyed
if(!(pvt?isEmptyDataObject:jQuery.isEmptyObject)(thisCache)){
return;
}
}
}
//SeejQuery.dataformoreinformation
//删除自定义数据缓存对象cache[id].data
if(!pvt){
deletecache[id].data;
//Don'tdestroytheparentcacheunlesstheinternaldataobject
//hadbeentheonlythingleftinit
if(!isEmptyDataObject(cache[id])){
return;
}
}
//Browsersthatfailexpandodeletionalsorefusetodeleteexpandoson
//thewindow,butitwillallowitonallotherJSobjects;otherbrowsers
//don'tcare
//Ensurethat`cache`isnotawindowobject#10080
//删除数据缓存对象cache[id]
if(jQuery.support.deleteExpando||!cache.setInterval){
deletecache[id];
}else{
cache[id]=null;
}
//Wedestroyedthecacheandneedtoeliminatetheexpandoonthenodetoavoid
//falselookupsinthecacheforentriesthatnolongerexist
//删除DOM元素上扩展的jQuery.expando属性
if(isNode){
//IEdoesnotallowustodeleteexpandopropertiesfromnodes,
//nordoesithavearemoveAttributefunctiononDocumentnodes;
//wemusthandleallofthesecases
if(jQuery.support.deleteExpando){
deleteelem[internalKey];
}elseif(elem.removeAttribute){
elem.removeAttribute(internalKey);
}else{
elem[internalKey]=null;
}
}
}
});
jQuery.fn.extend({
removeData:function(key){
returnthis.each(function(){
jQuery.removeData(this,key);
});
}
});
//checksacacheobjectforemptiness
functionisEmptyDataObject(obj){
for(varnameinobj){
//ifthepublicdataobjectisempty,theprivateisstillempty
if(name==="data"&&jQuery.isEmptyObject(obj[name])){
continue;
}
if(name!=="toJSON"){
returnfalse;
}
}
returntrue;
}
六、$.hasData(elem)
使用方法:
<!doctypehtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>jQuery.hasDatademo</title>
<scriptsrc="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<p>Results:</p>
<script>
var$p=jQuery("p"),p=$p[0];
$p.append(jQuery.hasData(p)+"");//false
$.data(p,"testing",123);
$p.append(jQuery.hasData(p)+"");//true
$.removeData(p,"testing");
$p.append(jQuery.hasData(p)+"");//false
$p.on("click",function(){});
$p.append(jQuery.hasData(p)+"");//true
$p.off("click");
$p.append(jQuery.hasData(p)+"");//false
</script>
</body>
</html>
$.hasData(elem)源码解析:
全选复制放进笔记$.extend({
hasData:function(elem){
elem=elem.nodeType?jQuery.cache[elem[jQuery.expando]]:elem[jQuery.expando];
return!!elem&&!isEmptyDataObject(elem);
//如果关联的数据缓存对象存在,并且含有数据,则返回true,否则返回false。这里用两个逻辑非运算符!把变量elem转换为布尔值
}
});