很多同学在项目中都喜欢将数据存储在HTMLElement属性上,如
1 2 <div data="some data">Test</div> 3 <script> 4 div.getAttribute('data'); // some data 5 </script>
给页面中div添加了自定义属性“data”及值“some data”。后续JS代码中使用getAttribute获取。
jQuery从1.2.3开始提供了data/removeData方法用来存储/删除数据。1.6.1代码片段
1 jQuery.extend({ 2 cache: {}, 3 4 // Please use with caution 5 uuid: 0, 6 7 ... 8 9 });
即给jQuery添加了静态字段/方法,有jQuery.cache/jQuery.uuid/jQuery.expando等。下面分别介绍
jQuery.cache 空对象,用来缓存。它的结构较复杂。
jQuery.uuid 自增唯一的数字。
jQuery.expando
字符串,使用Math.random生成,去掉了非数字字符。它作为HTMLElement或JS对象的属性名。
1 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。
1 <div>aa</div> 2 <script> 3 var div = document.getElementsByTagName('div')[0]; 4 $.hasData(div); // false 5 $.data(div, 'name','jack'); 6 $.hasData(div); // true 7 </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
1 // 为JS对象提供缓存 2 var myObj = {}; 3 $.data(myObj, 'name', 'jack'); 4 $.data(myObj, 'name'); // jack 5 6 // 为HTMLElement提供缓存 7 <div id="xx"></div> 8 <script> 9 var el = document.getElementById('xx'); 10 $.data(el, 'name', 'jack'); 11 $.data(el, 'name'); // jack 12 </script>
内部实现上也是有区别的,
1,为JS对象提供缓存时,直接将数据保存在JS对象上。cache为JS对象。此时会偷偷的给JS对象添加个属性(类似于jQuery16101803968874529044),属性值也是个JS对象。举例说明
1 var myObj = {}; 2 $.data(myObj, 'name', 'jack'); 3 console.log(myObj);
myObj的结构如下
1 myObj = { 2 jQuery16101803968874529044 : { 3 name : 'jack' 4 } 5 }
“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。举例说明
1 <div id="xx"></div> 2 <script> 3 var el = document.getElementById('xx'); 4 $.data(el, 'name', 'jack'); 5 console.log(el[jQuery.expando]); // 1 6 console.log(jQuery.cache); // {1 : {name:'jack'}} 7 </script>
el 上添加了属性jQuery.expando,值为id,这个id是从1开始递增的。而id又作为jQuery.cache的属性(key)。这样就HTMLElement就与jQuery.cache建立了联系。如图
不知注意到没有,jQuery.data还有第四个参数pvt,这个参数只在jQuery._data中使用。
1 // For internal use only. 2 _data: function( elem, name, data ) { 3 return jQuery.data( elem, name, data, true ); 4 },
jQuery._data从命名上就指定它是私有的,使用jQuery的客户端程序员不应该去调用该方法。jQuery的API文档上也不会公开它。
jQuery的数据缓存模块从1.2.3到1.6.1几乎每个版本都在变。jQuery._data的提出就是为了避免客户端程序员覆盖/重写了默写模块。如jQuery事件模块中事件handler等就使用jQuery.data存储,如果重写了该模块。那么事件模块将瘫痪。因此特意添加了pvt参数及jQuery._data方法。
但如果你刻意要破坏,那么还是可以做的。如下
1 <div id="xx">Test</div> 2 <script> 3 $('#xx').click(function(){ 4 alert('click'); 5 }); 6 7 // 语句1 8 $.data($('#xx')[0], 'events', '', true); 9 10 // 语句2 11 //$._data($('#xx')[0], 'events', ''); 12 </script>
整个jQuery.data设置(set)数据缓存的过程就是如此,理解的这个。取数据(get)的过程就好理解了。不重复。
用jQuery.extend方法扩展工具函数,jQuery.fn.extend调用jQuery.extend中扩展的方法缓存数据。
数据缓存源码:
1 /* Start Data cache*/ 2 var rbrace = /^(?:\{.*\}|\[.*\])$/,//花括号 3 rmultiDash = /([a-z])([A-Z])/g;//驼峰写法,大小写之间会被插入破折号 4 /**写入*/ 5 //在匹配的DOM元素(elem)上附加一个唯一ID,在$.cache中添加同样的ID属性,该ID属性的值是一个对象,其中存储了key/value的映射 6 7 //.data(key,value)用来设置保存数据 .data(key)用来查询数据 8 jQuery.extend({ 9 //数据存储在$.cache中,关键实现的核心 10 cache: {}, 11 //整型值,初始为0 调用data接口自动加一 生成新的唯一ID 分配ID用的seed 12 uuid: 0, 13 /* 唯一ID附加在$.expando命名的属性上,$.expando是动态生成的,避免命名冲突 移除非数字编号的 14 为了区分不同的jQuery实例存储的数据,前缀 + 版本号 + 随机数作为 key*/ 15 expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), 16 17 //以下元素没有data 除了Flash之外的object 18 noData: { 19 "embed": true,//用于播放一个多媒体对象 ,flash 音频 视频 20 "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",//用于向页面添加多媒体对象 flash 音频 视频 21 "applet": true//object用于IE浏览器 embed元素用于非IE浏览器 22 }, 23 24 //判断一个元素事发后有与之关联的数据(通过jQuery.data设置) 用在事件处理中 25 hasData: function( elem ) { 26 //如果是DOM,就从jQuery.cache中读取,关联的jQuery.cache和DOM元素的id存储在jQueyr.expando中 27 //如果非DOM 直接从elem上取,jQuery.expando属性中有数据 28 //elem的属性jQuery.expando 要么值是id 要么存储数据 29 elem = elem.nodeType ? jQuery.cache[elem[jQuery.expando]]:elem[jQuery.expando]; 30 return !!elem && !isEmptyDataObject(elem); 31 }, 32 /* 33 工具函数: 34 jQuery.data(elem,key,value) 在指定元素上存储、添加任意数据 处理了循环引用和内存泄露问题 35 jQuery.data(elem,key) 返回指定元素上name指定的值 36 jQuery.data(elem) 返回全部数据 37 38 pvt私有的 是否是内部使用的独立对象 pvt为true用于事件处理 39 40 myObj = { js缓存直接绑定在js对象上 41 jQuery16101803968874529044 : { 42 name : 'jack' 43 } 44 } 45 示例: 46 <div id="xx"></div> 47 <script> html是放在jquery.cache上面的 48 var el = document.getElementById('xx'); 49 $.data(el, 'name', 'jack'); 50 console.log(el[jQuery.expando]); // 1 51 console.log(jQuery.cache); // { 1 : { name:'jack'}} 52 </script> 53 54 HTMLElement -> jQuery.expando ->ID ->jQuery.cache 55 56 */ 57 /* 58 data部分的代码明确区分了JS对象 和 DOM对象的保存,为了解决部分浏览器的内存泄露问题。当DOM和JS对象之间出现循环引用时,GC就无法正确处理。 59 60 */ 61 data: function( elem, name, data, pvt /* Internal Use Only */ ) { 62 //如果属于noData中定义的元素,是否可以附加数据,不可以直接返回 63 if(!jQuery.acceptData(elem)){ 64 return; 65 } 66 67 var internalKey = jQuery.expando,//?? 68 getByName = typeof name === "string", 69 thisCache, 70 //区分处理DOM元素和JS对象 IE不能垃圾回收对象跨DOM和JS 因为IE6-7不能垃圾回收对象跨DOM对象和JS对象进行的引用属性 71 isNode = elem.nodeType, 72 73 //如果是DOM 使用全局jQuery.cache 74 //如果是JS对象,直接附加在对象上 75 cache = isNode ? jQuery.cache : elem, 76 77 //如果JS对象的cache已经存在,需要为JS对象定义一个ID 78 //如果DOM元素,直接取elem[jQuery.expando] 返回id 为递增的 79 //如果是JS对象,且JS对象的属性jQuery.expando存在,返回jQuery.expando 80 //var internalKey = jQuery.expando 81 id = isNode ? elem[jQuery.expando] : elem[jQuery.expando] && jQuery.expando; 82 //id不存在 id存在但是internalKey=jQuery.expando不存在 83 //data未定义,说明当前调用是查询数据,但是对象没有任何数据 直接返回 84 if((!id || (pvt && id && !cache[id][internalKey])) && getByName && data === undefined){ 85 return; 86 } 87 //HTMLElement -> jQuery.expando -> id -> jQuery.cache 88 //id不存在生成一个 设置id 89 if(!id){ 90 if(isNode){ 91 //HTMLElement添加属性(类似于jQuery16101803968874529044),属性值为数字 1 2 3 4 92 //用uuid递增分配唯一ID,只有DOM元素需要。需要存在全局cache中 93 elem[jQuery.expando] = id = ++ jQuery.uuid; 94 }else{ 95 //避免与其他属性冲突 “jQuery16101803968874529044”这个字符串在data内部命名为id 96 id = jQuery.expando; 97 } 98 } 99 100 //数据存储在一个映射对象中 清空原有的值 101 if(!cache[id]){ 102 cache[id] = {};//初始化存储对象 103 if(!isNode){ 104 cache[id].toJSON = jQuery.noop; 105 } 106 } 107 //用extend扩展cache,增加一个属性,用来保存数据。 108 //data接口接受对象和函数 浅拷贝 109 if(typeof name === "object" || typeof name === "function"){ 110 if(pvt){ 111 //id: 1 2 3 4 5 ... 112 cache[id][internalKey] = jQuery.extend(cache[id][internalKey],name); 113 }else{ 114 cache[id] = jQuery.extend(cache[id],name); 115 } 116 } 117 //存储了所有的数据的映射对象 118 thisCache = cache[id]; 119 120 //避免key冲突 121 if ( pvt ) { 122 if ( !thisCache[ internalKey ] ) { 123 thisCache[ internalKey ] = {};//设空对象 124 } 125 thisCache = thisCache[ internalKey ]; 126 } 127 128 if ( data !== undefined ) { 129 thisCache[ jQuery.camelCase( name ) ] = data; 130 } 131 //忽略 132 if ( name === "events" && !thisCache[name] ) { 133 return thisCache[ internalKey ] && thisCache[ internalKey ].events; 134 } 135 //如果name是字符串,返回data 不是 返回整个存储对象 136 return getByName ? thisCache[ jQuery.camelCase( name ) ] : thisCache; 137 }, 138 //删除数据 139 removeData: function( elem, name, pvt /* Internal Use Only */ ) { 140 //如果元素不能附加数据 141 if ( !jQuery.acceptData( elem ) ) { 142 return; 143 } 144 //internalKey定义唯一ID 145 var internalKey = jQuery.expando, 146 //是DOM对象 147 isNode = elem.nodeType, 148 149 //DOM对象用cache全局缓存 JS对象存在elem中 150 cache = isNode ? jQuery.cache : elem, 151 // 如果elem的jQuery.expando已经有值了,就重用 减少ID数 152 id = isNode ? elem[ jQuery.expando ] : jQuery.expando; 153 //缓存中没有这个元素 直接返回 154 if ( !cache[ id ] ) { 155 return; 156 } 157 //部分浏览器不支持deleteExpando ,在jQuery.support中检查过这个浏览器特性 失败的话 设null 158 if(jQuery.support.deleteExpando || cache != window){ 159 delete cache[id]; 160 }else{ 161 cache[id] = null; 162 } 163 164 var internalCache = cache[id][internalKey]; 165 //如果还有数据 就清空一次在设置 增加性能 166 if(internalCache){ 167 cache[id] = {}; 168 cache[id][internalKey] = internalCache; 169 //没有任何数据了 170 }else if(isNode){ 171 //如果支持delete 就删除 IE使用reomveAttribute 172 if(jQuery.support.deleteExpando){ 173 delete elem[jQuery.expando]; 174 }else if(elem.reomveAttribute){ 175 elem.reomveAttribute(jQuery.expando); 176 }else{ 177 elem[jQuery.expando] = null; 178 } 179 } 180 }, 181 _data: function( elem, name, data ) { 182 return jQuery.data(elem,name,data,true); 183 }, 184 185 //判断一个元素是否可以附加数据 186 acceptData: function( elem ) { 187 if(elem.nodeName){ 188 //embed object applet 以下元素没有data 189 var match = jQuery.noData[elem.nodeName.toLowerCase()]; 190 if(match){ 191 //getAttribute():查询属性的名字 返回false 192 return !(match === true || elem.getAttribute("classid") !== match); 193 } 194 } 195 return true; 196 } 197 }); 198 199 jQuery.fn.extend({ 200 //向被选元素添加数据 和读取数据 201 data: function( key, value ) { 202 var data = null;//初始化 203 //用于处理特殊key key是undefined 则认为取当前jQuery对象中第一个元素的全部数据 204 if(typeof key === "undefined"){ 205 if(this.length){ 206 data = jQuery.data(this[0]); 207 //Element 208 if(this[0].nodeType === 1){ 209 var attr = this[0].attributes,name; 210 for(var i = 0; l = attr.length;i<l;i++){ 211 name = attr[i].name; 212 if(name.indexOf("data-") === 0){ 213 name = jQuery.camelCase(name.substring(5)); 214 215 dataAttr(this[0],name,data[name]); 216 } 217 } 218 } 219 return data; 220 //key是对象 则对当前jQuery对象迭代调用$.fn.each 221 //在每一个匹配的元素上存储数据key 222 }else if(typeof key === "object" ){ 223 return this.each(function(){ 224 jQuery.data(this,key); 225 } 226 } 227 // 到这里,key是字符串 228 var parts = key.split("."); 229 parts[1] = parts[1] ? "." + parts[1] : ""; 230 //如果value为undefined,则 取当前jQuery对象中第一个元素指定名称的数据 231 if ( value === undefined ) { 232 data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); 233 234 // Try to fetch any internally stored data first 235 // 优先取内部数据 236 if ( data === undefined && this.length ) { 237 data = jQuery.data( this[0], key ); 238 // 读取HTML5的data属性 239 data = dataAttr( this[0], key, data ); 240 } 241 242 return data === undefined && parts[1] ? 243 this.data( parts[0] ) : 244 data; 245 246 // key是字符串,value不是undefined,则存储 247 } else { 248 return this.each(function() { 249 var $this = jQuery( this ), 250 args = [ parts[0], value ]; 251 // 触发事件 252 $this.triggerHandler( "setData" + parts[1] + "!", args ); 253 jQuery.data( this, key, value ); 254 $this.triggerHandler( "changeData" + parts[1] + "!", args ); 255 }); 256 } 257 }, 258 //删除数据 259 removeData: function( key ) { 260 return this.each(function() { 261 jQuery.removeData( this, key ); 262 }); 263 } 264 });