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转换为布尔值

}

});

相关推荐