jQuery中数据缓存$.data的用法及源码完全解析
一、实现原理:
对于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="https://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="https://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="https://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="https://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="https://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转换为布尔值 } });