zoukankan      html  css  js  c++  java
  • 【原创】jQuery1.8.2源码解析之jQuery.data

    数据缓存,jQuery现在支持两种:

    1. dom元素,数据存储在jQuery.cache中。

    2.普通js对象,数据存储在该对象中。

     以下是源代码:

      1 var rbrace = /^(?:\{.*\}|\[.*\])$/,
      2     rmultiDash = /([A-Z])/g;
      3 
      4 // 首先是对jQuery对象自身的扩展
      5 jQuery.extend({
      6     // 即jQuery.cache,负责存储dom元素的缓存数据
      7     cache: {},
      8 
      9     // removeData时,缓存的数据被清除,返回的当时对应的id,以便再利用
     10     deletedIds: [], 
     11 
     12     // Please use with caution
     13     // 将数据存储到jQuery.cache中时,需要唯一id,用它来维护
     14     uuid: 0,
     15 
     16     // Unique for each copy of jQuery on the page
     17     // Non-digits removed to match rinlinejQuery
     18     // 内部key(随即生成),之后会作为key添加到dom的属性集中,而key对应的value则是该dom对应的缓存对象
     19     expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
     20 
     21     // The following elements throw uncatchable exceptions if you
     22     // attempt to add expando properties to them.
     23     // 不能添加expando属性的dom
     24     // classid为'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'的object是可以的,比较特殊吧
     25     noData: {
     26         "embed": true,
     27         // Ban all objects except for Flash (which handle expandos)
     28         "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
     29         "applet": true
     30     },
     31 
     32     hasData: function( elem ) {
     33         elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
     34         return !!elem && !isEmptyDataObject( elem );
     35     },
     36 
     37     data: function( elem, name, data, pvt /* Internal Use Only */ ) {
     38         if ( !jQuery.acceptData( elem ) ) {
     39             return;
     40         }
     41 
     42         var thisCache, ret,
     43             internalKey = jQuery.expando,
     44             getByName = typeof name === "string",
     45 
     46             // We have to handle DOM nodes and JS objects differently because IE6-7
     47             // can't GC object references properly across the DOM-JS boundary
     48             // 也就是说dom元素和普通js对象要进行不同的处理
     49             // 原因好像是是垃圾回收不能正确处理添加到dom元素的引用
     50             isNode = elem.nodeType,
     51 
     52             // Only DOM nodes need the global jQuery cache; JS object data is
     53             // attached directly to the object so GC can occur automatically
     54             // dom元素我们借用全局jQuery.cache来存储数据
     55             // 普通的js对象则直接将数据存储到对象中,垃圾回收可以自动处理
     56             cache = isNode ? jQuery.cache : elem,
     57 
     58             // Only defining an ID for JS objects if its cache already exists allows
     59             // the code to shortcut on the same path as a DOM node with no cache
     60             // 1. 如果是dom元素,返回dom元素expando对应的id(值可能为undefined)
     61             // 2. 如果是普通js对象,分两种情况:
     62             //    2.1 如果js对象存在expando对应的值,即代表有缓存数据,则立即返回expando作为id
     63             //    2.2 如果没有对应值,则代表没有缓存数据,此时返回undefined
     64             // 也就是说如果id不为空,那么肯定是有存储数据过的
     65             id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
     66 
     67         // Avoid doing any more work than we need to when trying to get data on an
     68         // object that has no data at all
     69         // 如果id不存在(表示不存在缓存)
     70         // 或者id存在,但是缓存为空
     71         // 又或者此时数据是私有的(pvt为true,仅为内部使用,此时只操控到cache[id]这一层)
     72         // 又或者数据不是私有的,但是对应的数据(data)为空
     73         // 以上条件之一成立后,
     74         // 再加上,getByName && data === undefined(表示是取数据)这个条件,直接return就可以了,因为没有数据取
     75         // 如果getByName为false,那么将初始化缓存对象(也为后来可能的name为object或者function时,extend做准备)
     76         if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
     77             return;
     78         }
     79         // 如果id为空,表示dom元素或者普通js对象没有缓存
     80         if ( !id ) {
     81             // Only DOM nodes need a new unique ID for each element since their data
     82             // ends up in the global cache
     83             // dom元素需要唯一id,因为它的数据将存在全局的jQuery.cache中
     84             if ( isNode ) {
     85                 elem[ internalKey ] = id = jQuery.deletedIds.pop() || ++jQuery.uuid;
     86             } else {
     87             // 普通js对象的id都是expando
     88                 id = internalKey;
     89             }
     90         }
     91 
     92         // 如果缓存为空,则之前没有存储过数据,此时需要进行必要的初始化
     93         if ( !cache[ id ] ) {
     94             // 创建缓存对象(理解为一个存放键值对的集合)
     95             cache[ id ] = {};
     96 
     97             // Avoids exposing jQuery metadata on plain JS objects when the object
     98             // is serialized using JSON.stringify
     99             // 普通js对象需要在它的缓存对象中添加toJSON方法,其中jQuery.noop只是有个空函数,什么都不做
    100             // 这里的目的是:在使用JSON.stringify(elem)序列化该js对象时,使它的缓存对象不参与序列化(空函数返回空)
    101             // 而dom元素是无法使用JSON.stringify(dom)的,会报错Converting circular structure to JSON
    102             if ( !isNode ) {
    103                 cache[ id ].toJSON = jQuery.noop;
    104             }
    105         }
    106 
    107         // An object can be passed to jQuery.data instead of a key/value pair; this gets
    108         // shallow copied over onto the existing cache
    109         // 就是说在适用jQuery.data()缓存数据时,除了传递key/value键值对外,还可以传递一个对象,或者一个函数(返回一个对象)
    110         // 这样的结果是:传递的对象将会被extend到缓存中去
    111         if ( typeof name === "object" || typeof name === "function" ) {
    112             if ( pvt ) {
    113                 // 私有数据,这里我明白了大概pvt的用处
    114                 // cache[id].data 对象是用来存储用户自定义数据
    115                 // cache[id] 则存储的是系统内部数据,比如之前说的toJSON
    116                 // pvt不为空,则处理用户自定义数据,定位到cache[id].data这一层
    117                 // pvt为空,则处理系统内部数据,定位到cache[id]这一层
    118                 cache[ id ] = jQuery.extend( cache[ id ], name );
    119             } else {
    120                 cache[ id ].data = jQuery.extend( cache[ id ].data, name );
    121             }
    122         }
    123 
    124         thisCache = cache[ id ];
    125 
    126         // jQuery data() is stored in a separate object inside the object's internal data
    127         // cache in order to avoid key collisions between internal data and user-defined
    128         // data.
    129         // 就是说为了防止系统内部数据和用户自定义数据的key发生冲突,才将用户数据包在thisCache.data中,
    130         // 系统内部数据就是thisCache中
    131         if ( !pvt ) {
    132             if ( !thisCache.data ) {
    133                 thisCache.data = {};
    134             }
    135 
    136             //此时thisCache指向真正的数据缓存(集合)
    137             thisCache = thisCache.data;
    138         }
    139 
    140         // 如果data不为undefined,则表示这是在设置数据(set),那么进行负值缓存操作
    141         // jQuery.camelCase( name )将name驼峰化
    142         if ( data !== undefined ) {
    143             thisCache[ jQuery.camelCase( name ) ] = data;
    144         }
    145 
    146         // Check for both converted-to-camel and non-converted data property names
    147         // If a data property was specified
    148         // 返回指定name的数据,包括取数据和设置数据,都会返回。
    149         if ( getByName ) {
    150 
    151             // First Try to find as-is property data
    152             // 首先尝试取数据
    153             ret = thisCache[ name ];
    154 
    155             // Test for null|undefined property data
    156             // 如果ret为null或者undefined,则尝试将name驼峰化再尝试取数据(因为有可能之前name就被驼峰化)
    157             if ( ret == null ) {
    158                 // Try to find the camelCased property
    159                 ret = thisCache[ jQuery.camelCase( name ) ];
    160             }
    161         } else {
    162             // 没有指定name,则返回整个缓存对象
    163             ret = thisCache;
    164         }
    165         //返回数据
    166         return ret;
    167     },
    168 
    169     removeData: function( elem, name, pvt /* Internal Use Only */ ) {
    170         if ( !jQuery.acceptData( elem ) ) {
    171             return;
    172         }
    173 
    174         var thisCache, i, l,
    175 
    176             isNode = elem.nodeType,
    177 
    178             // See jQuery.data for more information
    179             cache = isNode ? jQuery.cache : elem,
    180             id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
    181 
    182         // If there is already no cache entry for this object, there is no
    183         // purpose in continuing
    184         // 没有缓存,直接退出
    185         if ( !cache[ id ] ) {
    186             return;
    187         }
    188 
    189         // 如果有传入name,那么删除指定name对应的数据
    190         // 否则删除所有缓存,后面的代码有这一步处理
    191         if ( name ) {
    192             // 这里还是一样,通过内部pvt指定缓存层级,用户自定义数据层和系统内部数据层
    193             thisCache = pvt ? cache[ id ] : cache[ id ].data;
    194 
    195             if ( thisCache ) {
    196 
    197                 // Support array or space separated string names for data keys
    198                 // 支持单个的key
    199                 // 数组,多个key,如:[key1, key2, key3, ...]
    200                 // 字符串,多个key,用空格隔开,如:'key1 key2 key3 ...'
    201 
    202                 //不是数组的情况,最终转换为数组形式
    203                 if ( !jQuery.isArray( name ) ) {
    204 
    205                     // try the string as a key before any manipulation
    206                     // 首先直接查找
    207                     if ( name in thisCache ) {
    208                         name = [ name ];        //转换成数组形式,便于后面统一操作
    209                     } else {
    210 
    211                         // split the camel cased version by spaces unless a key with the spaces exists
    212                         name = jQuery.camelCase( name );
    213                         // 驼峰化后再查找
    214                         if ( name in thisCache ) {
    215                             name = [ name ];
    216                         // 用字符串转换为数组
    217                         } else {
    218                             name = name.split(" ");
    219                         }
    220                     }
    221                 }
    222                 // 统一用数组进行删除操作
    223                 // 有一个疑问就是,如果数组元素没有被驼峰化,应该会出错?!
    224                 for ( i = 0, l = name.length; i < l; i++ ) {
    225                     // delete thisCache[ jQuery.camelCase(name[i]) ];
    226                     delete thisCache[ name[i] ];
    227                 }
    228 
    229                 // If there is no data left in the cache, we want to continue
    230                 // and let the cache object itself get destroyed
    231                 // 如果缓存不为空,则退出
    232                 // 否则,需要进行下一步的清理工作,因为此时缓存为空了嘛
    233                 if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
    234                     return;
    235                 }
    236             }
    237         }
    238 
    239         // See jQuery.data for more information
    240         if ( !pvt ) {
    241             // 去除data属性
    242             delete cache[ id ].data;
    243 
    244             // Don't destroy the parent cache unless the internal data object
    245             // had been the only thing left in it
    246             // 当data处理过后需要检测cache[id],因为此时cache[id]可能处于空的状态(这里的所谓的空在isEmptyDataObject有说明)
    247             if ( !isEmptyDataObject( cache[ id ] ) ) {
    248                 return;
    249             }
    250         }
    251 
    252         // Destroy the cache
    253         // 如果是dom元素,除了jQuery.cache清理完毕后,还要处理dom元素自身,因为绑定了一个id嘛
    254         if ( isNode ) {
    255             jQuery.cleanData( [ elem ], true );
    256 
    257         // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
    258         } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
    259             delete cache[ id ];
    260 
    261         // When all else fails, null
    262         } else {
    263             cache[ id ] = null;
    264         }
    265     },
    266 
    267     // For internal use only.
    268     // 内部适用,这里设置pvt为true,返回内部数据,定位到cache[id]这一层
    269     _data: function( elem, name, data ) {
    270         return jQuery.data( elem, name, data, true );
    271     },
    272 
    273     // A method for determining if a DOM node can handle the data expando
    274     // 根据上面的jQuery.noData属性判断dom该元素是否可以添加expando属性(即dom是否允许添加数据)
    275     // 其中,classid为'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'的object是可以的,比较特殊吧
    276     acceptData: function( elem ) {
    277         var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
    278 
    279         // nodes accept data unless otherwise specified; rejection can be conditional
    280         return !noData || noData !== true && elem.getAttribute("classid") === noData;
    281     }
    282 });
    283 
    284 // 接下来是对jQuery对象($(selector))的扩展
    285 jQuery.fn.extend({
    286     data: function( key, value ) {
    287         var parts, part, attr, name, l,
    288             elem = this[0],
    289             i = 0,
    290             data = null;
    291 
    292         // Gets all values
    293         // 如果为连key的值都未指定,那么返回的所有数据
    294         // 如:$dom.data();
    295         if ( key === undefined ) {
    296             if ( this.length ) {
    297                 // 先从jquery缓存中取出所有数据
    298                 data = jQuery.data( elem );
    299 
    300                 // 对于元素节点而言,数据可以来自两个地方:
    301                 // 1. jQuery.cache缓存中,之前手动存进去的,如:$dom.data('data1', value1);
    302                 // 2. 来自html标签的以data-开头的属性,之后该属性的数据也会被存储到jQuery.cache缓存中,
    303                 //    (属性名采用驼峰的形式)避免每次都要去html标签里去匹配并取值
    304                 //    如:<div data-data-first="{a:1,b:2}" data-data-second="hello">hello world</div>
    305                 //    当使用$dom.data()时,会获取到:
    306                 // {
    307                 //     dataFirst : {
    308                 //         a : 1,
    309                 //         b : 2
    310                 //     }
    311                 //     dataSecond : 'hello world'
    312                 // }
    313 
    314                 // 通过缓存中的内部属性parsedAttrs,分析html标签属性所带的数据是否被解析过(即存到jQuery过缓存中)
    315                 // 解析过了,那么这里就没必要再解析一遍了,上面一步就已经取到数据了
    316                 if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
    317                     attr = elem.attributes;
    318                     // 遍历dom节点的属性列表
    319                     for ( l = attr.length; i < l; i++ ) {
    320                         name = attr[i].name;
    321                         // 对于属性名以data-开头的属性进行取值存储操作
    322                         if ( name.indexOf( "data-" ) === 0 ) {
    323                             // 首先name去除data-,并将剩余的字符驼峰化
    324                             name = jQuery.camelCase( name.substring(5) );
    325 
    326                             dataAttr( elem, name, data[ name ] );
    327                         }
    328                     }
    329                     // 标记html标签上的数据已经解析过
    330                     jQuery._data( elem, "parsedAttrs", true );
    331                 }
    332             }
    333 
    334             return data;
    335         }
    336 
    337         // Sets multiple values
    338         // 传递对象(键值对)作为data缓存,此时是对jQuery对象列表进行each操作
    339         if ( typeof key === "object" ) {
    340             return this.each(function() {
    341                 jQuery.data( this, key );
    342             });
    343         }
    344 
    345         parts = key.split( ".", 2 );
    346         parts[1] = parts[1] ? "." + parts[1] : "";
    347         part = parts[1] + "!";
    348 
    349         return jQuery.access( this, function( value ) {
    350 
    351             if ( value === undefined ) {
    352                 data = this.triggerHandler( "getData" + part, [ parts[0] ] );
    353 
    354                 // Try to fetch any internally stored data first
    355                 if ( data === undefined && elem ) {
    356                     // 首先从jQuery缓存中获取
    357                     data = jQuery.data( elem, key );
    358                     // 再从html标签里面获取(可见标签数据的优先级高)
    359                     data = dataAttr( elem, key, data );
    360                 }
    361 
    362                 return data === undefined && parts[1] ?
    363                     this.data( parts[0] ) :
    364                     data;
    365             }
    366 
    367             parts[1] = value;
    368             this.each(function() {
    369                 var self = jQuery( this );
    370 
    371                 self.triggerHandler( "setData" + part, parts );
    372                 jQuery.data( this, key, value );
    373                 self.triggerHandler( "changeData" + part, parts );
    374             });
    375         }, null, value, arguments.length > 1, null, false );
    376     },
    377 
    378     removeData: function( key ) {
    379         return this.each(function() {
    380             jQuery.removeData( this, key );
    381         });
    382     }
    383 });
    384 
    385 function dataAttr( elem, key, data ) {
    386     // If nothing was found internally, try to fetch any
    387     // data from the HTML5 data-* attribute
    388     // 如果data为空,且elem为元素节点,那么从标签的数据属性取数据(遵循html5)
    389     if ( data === undefined && elem.nodeType === 1 ) {
    390         // 将驼峰化转换成'-'连接的小写字符串
    391         var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
    392         // 获取dom上的对应属性
    393         data = elem.getAttribute( name );
    394         // 如果该属性存在,此时data为字符串,下面将进行根据数据类型进行数据的格式化
    395         if ( typeof data === "string" ) {
    396             try {
    397                 // 布尔型
    398                 data = data === "true" ? true :
    399                 data === "false" ? false :
    400                 // null
    401                 data === "null" ? null :
    402                 // Only convert to a number if it doesn't change the string
    403                 // +data用来测试类型是否为数字
    404                 +data + "" === data ? +data :
    405                 // 对象和数组
    406                 rbrace.test( data ) ? jQuery.parseJSON( data ) :
    407                     data;
    408             } catch( e ) {}
    409 
    410             // Make sure we set the data so it isn't changed later
    411             // 将格式化的数据存在jQuery.cache缓存。
    412             //(注意这里存的是jQuery.cache中,也就是说之前通过$dom.data()获取的对象,因为是引用,所以此时也是有值的)
    413             jQuery.data( elem, key, data );
    414 
    415         } else {
    416             // 如果该属性不存在,此时data为null,将其转换为undefined
    417             data = undefined;
    418         }
    419     }
    420     // 返回标签属性数据
    421     return data;
    422 }
    423 
    424 // checks a cache object for emptiness
    425 // 内部使用,用于检测cache[id]这一层是否为空
    426 // 其中,toJSON 不参与检测,也就是说只有它存在时,也算是空
    427 // 其中,data 参与检测,如果data不为空,整个cache[id]则不为空
    428 function isEmptyDataObject( obj ) {
    429     var name;
    430     for ( name in obj ) {
    431 
    432         // if the public data object is empty, the private is still empty
    433         // 对于data属性,需要额外判断data里面是否有数据
    434         // 如果没有,则data为空,那么跳过data,继续检测
    435         // 否则将在下面的返回false,表示不为空
    436         if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
    437             continue;
    438         }
    439         // toJSON 这个方法可以认为是是内置的,可以忽略它。
    440         // 如果不是toJSON 一律返回false,表示还有其他数据
    441         if ( name !== "toJSON" ) {
    442             return false;
    443         }
    444     }
    445 
    446     return true;
    447 }
  • 相关阅读:
    bzoj1901 Zju2112 Dynamic Rankings
    bzoj3932 [CQOI2015]任务查询系统
    poj2104 K-th Number
    splay模板整理
    bzoj1500 [NOI2005]维修数列
    bzoj3223 Tyvj 1729 文艺平衡树
    bzoj1503 [NOI2004]郁闷的出纳员
    bzoj3224 Tyvj 1728 普通平衡树
    用CSS截断字符串
    发布一款仿天猫产品放大镜JQuery插件
  • 原文地址:https://www.cnblogs.com/lovesueee/p/2737839.html
Copyright © 2011-2022 走看看