zoukankan      html  css  js  c++  java
  • 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 });
  • 相关阅读:
    Redis持久化
    Java多线程面试题
    Spring学习总结(1)-注入方式
    SpringCloud常用注解
    Linux安装Redis
    Linux系统安装MySQL
    [转]Java CPU 100% 排查技巧
    ImportError: attempted relative import with no known parent package
    python出现Non-ASCII character 'xe6' in file statistics.py on line 19, but no encoding declared错误
    10个不为人知的 Python 冷知识
  • 原文地址:https://www.cnblogs.com/colorstory/p/2612102.html
Copyright © 2011-2022 走看看