jQuery源码分析系列:数据缓存

很多同学在项目中都喜欢将数据存储在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 });

 

 

 

 

posted on 2013-04-23 15:28  color_story  阅读(190)  评论(0编辑  收藏  举报

导航