先前在分析Sizzle的时候分析到Sizzle有自己的缓存机制,点击这里查看。不过Sizzle的缓存只是对内使用的(内部自己存,自己取)。接下来分析jQuery可以对外使用的缓存(可存可取)。

  首先需要明白jQuery缓存需要解决什么问题,实现它的意义?

  jQuery缓存要解决的是在往DOM节点添加数据(这些数据往往和该DOM节点紧密相关),但是给DOM添加数据或自定义属性可能起内存泄漏(DOM发生缓存泄漏导致DOM的数据没法被删除,那么添加到DOM的数据也无法被回收。久而久之,会出现一大片内存得不到释放。),所以应该要尽量避免这样做。更好的解决方法是使用一种低耦合的方式让DOM和缓存数据能够联系起来。

  jQuery怎么做?

  jQuery定义了一个属性cache = {}来保存所有的缓存数据。在DOM节点上添加一个expando的值(expando的值等于”jQuery”+当前时间)为属性名称的属性,这个属性的值id = dom[jQuery.expando]用来查找jQuery.cache上对应的缓存数据:jQuery.cache[id].data 即为DOM对应的缓存数据。为了保证了id 的全局唯一性,这个id使用jQuery.guid自增。例如

  1. $("#demo").data("name","chua");
  2. $("#demo").data("name");//"chua"

  jQuery底层接口还做了拓展,不仅仅能缓存在DOM节点的数据,还可以缓存非DOM节点的对象的数据。这种方式最终数据是附加到了对象obj自己身上,而没有使用全局缓存jQuery.cache。该方式也会在对象obj上添加一个expando的值为属性名称的属性,缓存数据保存在obj[jQuery.expando].data上。不过这个一般不推荐外部使用,因为调用的是更底层的api: jQuery.data,而非$(...).data。例子:

  1. var obj = {};
  2. $.data(obj,'name','chua');
  3. $.data(obj,'name');//"chua"

  

  接下来我们开始解析源码。

  1. jQuery.fn.extend({
  2. data: function( key, value ) {… },//内部使用基础api:jQuery.data来实现,可以使用参数(key,value),也可以使用参数(obj)
  3. removeData: function( key ) {…}//内部使用基础api: jQuery.removeData来实现
  4. });

所以我们主要看底层基础API部分

  1. jQuery.extend({
  2. cache: {},
  3. // 每个jQuery拷贝都有一个其唯一的标志。比如你的页面有两个iframe且每个iframe都用到的jQuery。name你的两个iframe就有两份jQuery拷贝。
  4. expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
  5. // 下面的元素将抛出不可捕获的异常,如果你尝试给他们添加expando属性
  6. //主要用在acceptData函数中确定元素是否可以添加expando属性
  7. noData: {
  8. "embed": true,
  9. // Ban all objects except for Flash (which handle expandos)
  10. "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
  11. "applet": true
  12. },
  13. hasData: function( elem ) {…},
  14.   data: function( elem, name, data ) {return internalData( elem, name, data );},
  15.   removeData: function( elem, name ) {return internalRemoveData( elem, name );},
  16. // 内部使用
  17. _data: function( elem, name, data ) {return internalData( elem, name, data, true );},
  18. _removeData: function( elem, name ) {return internalRemoveData( elem, name, true );},
  19. // 用来确定DOM节点是否能够添加expando 数据
  20. acceptData: function( elem ) {
  21. // non-element不能添加数据
  22. if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
  23. return false;
  24. }
  25. var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
  26. // nodes accept data unless otherwise specified; rejection can be conditional
  27. return !noData || noData !== true && elem.getAttribute("classid") === noData;
  28. }
  29. });

  所以归根结底缓存数据和取出缓存数据是使用内部函数internalData( elem, name, data, pvt /* Internal Use Only */ )

  

a. 存取缓存的内部函数function internalData( elem, name, data, pvt /* Internal Use Only */ )


  internalData的处理流程为:

  1.判断如果元素不支持添加属性的直接返回。取出缓存容器和缓存中使用的ID备用。这里面粉两种情况,如果传递的对象elem是DOM对象,则缓存取全局缓存容器jQuery.cache,id取elem[jQuery.expando](如果没有的话使用jQuery.guid自增值设置一个);如果elem不是DOM对象,则缓存容器取对象本身elem,id取jQuery.expando

  1. var thisCache, ret,
  2. //获取每份jquery拷贝的标志
  3. internalKey = jQuery.expando,
  4. getByName = typeof name === "string",
  5.  
  6. //我们将DOM节点和JS对象区分开来,因为IE6-7不能跨越DOM-JS界限正确回收对象引用
  7. //所有DOM节点上没有附加数据,而是存放再来全局jQuery缓存jQuery.cache中
  8. isNode = elem.nodeType,
  9.  
  10. //只有DOM节点需要全局jQuery缓存;js对象数据直接附加到对象上使得垃圾回收机制能够自动回收
  11. cache = isNode ? jQuery.cache : elem,
  12.  
  13. //假如JS对象的缓存已经存在,则拷贝标志作为一个访问ID。
  14. //如果是DOM对象,则拷贝标志作为一个属性附加到dom上,这个属性的值作为访问全局缓存的一个路径ID
  15. id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
  16.  
  17. //预先处理要获取数据,但是缓存中没有数据的情况。这种情况应当直接返回
  18. if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
  19. return;
  20. }
  21.  
  22. if ( !id ) {
  23. //只有dom节点的每个元素都需要唯一的ID,直到他们的数据在全局缓存中清除
  24. if ( isNode ) {
  25. //启用删除的id中最后一个id,或是对象的全局GUID统计
  26. elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++;
  27. } else {
  28. id = internalKey;
  29. }
  30. }

  2.取出的缓存容器cache[ id ]如果不存在就初始化为空对象

  1. //如果缓存中没有数据
  2. if ( !cache[ id ] ) {
  3. //初始化对象
  4. cache[ id ] = {};
  5.  
  6. //避免在对象使用JSON.stringify序列化的时候暴露jQuery的元数据给普通对象
  7. if ( !isNode ) {
  8. cache[ id ].toJSON = jQuery.noop;
  9. }
  10. }

  3.cache[ id ]并非真正的缓存数据,真正的缓存数据保存在cache[ id ].data上。在初始化之前需要对特殊情况(要缓存的数据是对象,且没指定缓存名称的时候,意味着要替换掉原来的整个缓存。比如:$.data(elem,{"name": "chua"})。)处理。如果cache[ id ].data不存在则初始化他。

  1. //使用对象的话用来替代key/value这种成对的方式。
  2. //比如$.data(document,{name:'chenhua'})
  3. //添加后,直接通过$.data(document,'name')就可以获取
  4. if ( typeof name === "object" || typeof name === "function" ) {
  5. //内部使用的时候
  6. if ( pvt ) {
  7. //内部使用直接添加到cache[ id ]上
  8. cache[ id ] = jQuery.extend( cache[ id ], name );
  9.  
  10. } else {
  11. //添加缓存数据到cache[ id ].data上
  12. cache[ id ].data = jQuery.extend( cache[ id ].data, name );
  13. }
  14. }
  15. thisCache = cache[ id ];
  16.  
  17. //非内部使用
  18. if ( !pvt ) {
  19. //如果.data没有初始化则先初始化
  20. if ( !thisCache.data ) {
  21. thisCache.data = {};
  22. }
  23.  
  24. thisCache = thisCache.data;
  25. }

  4.如果是存数据,则存之,返回整个缓存;取数据则取之,返回取得的数据

  1. //添加缓存数据
  2. if ( data !== undefined ) {
  3. thisCache[ jQuery.camelCase( name ) ] = data;
  4. }
  5.  
  6. //如果name是字符串,则返回对应的数据,否则返回整个缓存
  7. if ( getByName ) {
  8. //通过本身neme或不同浏览器的驼峰写法获取缓存
  9. ret = thisCache[ name ];
  10. if ( ret == null ) {
  11. ret = thisCache[ jQuery.camelCase( name ) ];
  12. }
  13. } else {
  14. ret = thisCache;
  15. }
  16. return ret;

  ok。流程就到这里。

  解析完jQuery.data的流程以后我们来做一组实验,区分高级api:$(...).data和底层api:jQuery.data的区别。在jQuery的官方文档中,提示用户jQuery.data是一个低级的方法,应该用$(...).data()方法来代替。$.data( element, key, value )可以对DOM元素附加任何类型的数据,但应避免循环引用而导致的内存。

  1. var t1=$(document);
  2. var t2=$(document);
  3.  
  4. //=======第一组$(…).data()方法
  5. t1.data('age',0);
  6. t2.data('age',1);
  7. t1.data('age') //1
  8. t2.data('age') //1
  9.  
  10. //=======第二组$.data()方法
  11. $.data(t1,"name","chua")
  12. $.data(t2,"name","yling")
  13. $.data(t1,"name") //chua
  14. $.data(t2,"name") //yling

  可以看出其中的不同吧。使用$(...).data最终传递给internalData的第一个参数elem是节点对象document,所以使用全局缓存jQuery.cache保存。而使用$.data方式最终传递给internalData的第一个参数elem是jQuery对象[document],这样的属性结果是保存在对象自己身上,而t1和t2是不同的对象,分别取自己对象上的缓存,结果当然不同了。

  var t1=$(document);t1.data('age',0);的缓存效果

  

  $.data(t1,"name","chua")的缓存效果

  

  

b. 删除缓存


  使用$(...).data(key,value)方式保存的缓存直接使用$(...).removeData(key)来删除缓存。当然也可以使用低级方法$.data(elem,key,value)来缓存数据,使用$.removeData(elem,key)来删除缓存,但是建议不要使用低级方法。删除缓存最终会调用内部函数internalRemoveData( elem, name, pvt/*内部使用,默认为false*/ )来处理。我们跟踪一下处理流程

  1.判断如果元素不支持添加属性的直接返回。取出缓存容器和相应的id。如果缓存容器不存在则直接返回

  1. //元素不支持添加属性的直接返回
  2. if ( !jQuery.acceptData( elem ) ) {
  3. return;
  4. }
  5.  
  6. var i, l, thisCache,
  7. isNode = elem.nodeType,
  8. //详细信息查看jQuery.data源码
  9. cache = isNode ? jQuery.cache : elem,
  10. id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
  11.  
  12. //缓存中没有数据直接返回
  13. if ( !cache[ id ] ) {
  14. return;
  15. }

  2.如果有需要删除的缓存名称(参数name,可以是字符串【如果要删除多个缓存名指定的缓存可以使用空格隔开】,也可以是数组),则根据名称删除元素。删除后如果缓存不是空对象则返回。

  1. if ( name ) {
  2. //pvt内部使用,默认为false(undefined)
  3. thisCache = pvt ? cache[ id ] : cache[ id ].data;
  4. //缓存对象
  5. if ( thisCache ) {
  6. //name为字串
  7. if ( !jQuery.isArray( name ) ) {
  8.  
  9. // name就是一个key
  10. if ( name in thisCache ) {
  11. name = [ name ];
  12. } else {
  13.  
  14. //处理ie兼容,如果name是空格相连的字符串,使用split分割
  15. name = jQuery.camelCase( name );
  16. if ( name in thisCache ) {
  17. name = [ name ];
  18. } else {
  19. name = name.split(" ");
  20. }
  21. }
  22. //name为数组
  23. } else {
  24. //如果name是key的数组
  25. // 当数据初始化完成,通过("key", "val")签名,
  26. // keys将转化成骆驼写法.
  27. // 如果没有办法告知_how_这种key被添加,移除原始类型的key和骆驼写法的key
  28. name = name.concat( jQuery.map( name, jQuery.camelCase ) );
  29. }
  30.  
  31. for ( i = 0, l = name.length; i < l; i++ ) {
  32. delete thisCache[ name[i] ];
  33. }
  34.  
  35. //如果缓存不为空则返回,如果为空则我们在后面通过delete删除该对象
  36. if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
  37. return;
  38. }
  39. }
  40. }

  3.如果没有传递要删除的缓存名,表示要删除全部缓存。删除缓存后如果缓存容器不为空对象则返回

  1. //详细信息查看jQuery.data源码
  2. if ( !pvt ) {
  3. delete cache[ id ].data;
  4. //cache不为空则返回
  5. if ( !isEmptyDataObject( cache[ id ] ) ) {
  6. return;
  7. }
  8. }

  4.走到最后一步,表示缓存容器内的缓存都被清空了,删除缓存容器

  1. //走到这一步表示cache中没有数据了,销毁cache
  2. if ( isNode ) {
  3. jQuery.cleanData( [ elem ], true );
  4.  
  5. // 当支持删除expandos 或'cache'不是window (#10080)
  6. } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
  7. delete cache[ id ];
  8.  
  9. // 当所有都失败时,处理为null
  10. } else {
  11. cache[ id ] = null;
  12. }

  附上完整源码

  1. function internalRemoveData( elem, name, pvt ) {
  2. //元素不支持添加属性的直接返回
  3. if ( !jQuery.acceptData( elem ) ) {
  4. return;
  5. }
  6.  
  7. var i, l, thisCache,
  8. isNode = elem.nodeType,
  9. //详细信息查看jQuery.data源码
  10. cache = isNode ? jQuery.cache : elem,
  11. id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
  12.  
  13. //缓存中没有数据直接返回
  14. if ( !cache[ id ] ) {
  15. return;
  16. }
  17.  
  18. if ( name ) {
  19. //pvt内部使用,默认为false(undefined)
  20. thisCache = pvt ? cache[ id ] : cache[ id ].data;
  21. //缓存对象
  22. if ( thisCache ) {
  23. //name为字串
  24. if ( !jQuery.isArray( name ) ) {
  25.  
  26. // name就是一个key
  27. if ( name in thisCache ) {
  28. name = [ name ];
  29. } else {
  30.  
  31. //处理ie兼容,如果name是空格相连的字符串,使用split分割
  32. name = jQuery.camelCase( name );
  33. if ( name in thisCache ) {
  34. name = [ name ];
  35. } else {
  36. name = name.split(" ");
  37. }
  38. }
  39. //name为数组
  40. } else {
  41. //如果name是key的数组
  42. // 当数据初始化完成,通过("key", "val")签名,
  43. // keys将转化成骆驼写法.
  44. // 如果没有办法告知_how_这种key被添加,移除原始类型的key和骆驼写法的key
  45. name = name.concat( jQuery.map( name, jQuery.camelCase ) );
  46. }
  47.  
  48. for ( i = 0, l = name.length; i < l; i++ ) {
  49. delete thisCache[ name[i] ];
  50. }
  51.  
  52. //如果缓存不为空则返回,如果为空则我们在后面通过delete删除该对象
  53. if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
  54. return;
  55. }
  56. }
  57. }
  58.  
  59. //详细信息查看jQuery.data源码
  60. if ( !pvt ) {
  61. delete cache[ id ].data;
  62. //cache不为空则返回
  63. if ( !isEmptyDataObject( cache[ id ] ) ) {
  64. return;
  65. }
  66. }
  67.  
  68. //走到这一步表示cache中没有数据了,销毁cache
  69. if ( isNode ) {
  70. jQuery.cleanData( [ elem ], true );
  71.  
  72. // 当支持删除expandos 或'cache'不是window (#10080)
  73. } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
  74. delete cache[ id ];
  75.  
  76. // 当所有都失败时,处理为null
  77. } else {
  78. cache[ id ] = null;
  79. }
  80. }

  如果觉得本文不错,请点击右下方【推荐】!

jQuery-1.9.1源码分析系列(四) 缓存系统的更多相关文章

  1. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  2. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  3. jQuery源码分析系列(转载来源Aaron.)

    声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...

  4. jQuery源码分析系列——来自Aaron

    jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...

  5. jQuery-1.9.1源码分析系列完毕目录整理

    jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...

  6. jquery2源码分析系列

    学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...

  7. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  8. MyCat源码分析系列之——SQL下发

    更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...

  9. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  10. MyCat源码分析系列之——前后端验证

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat前端验证 MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -pr ...

随机推荐

  1. jquery/zepto 圣诞节雪花飞扬

    下载地址: http://www.html5tricks.com/jquery-html5-christ-snow.html 演示地址: http://www.html5tricks.com/jque ...

  2. java 心得

    11. 最后的笑声 package javaBookPractice; public class LastLaugh { public static void main(String[] args) ...

  3. C ReadProcessMemory

    ReadProcessMemory函数用于读取其他进程的数据. BOOL STDCALL ReadProcessMemory ( HANDLE hProcess, LPCVOID lpBaseAddr ...

  4. Android动画

    [浅谈Android动画] 总共四种:Tween Animation变换动画.Frame Animation帧动画 Layout Animation布局动画.Property Animation 属性 ...

  5. SQL Server 2016 CTP2.2 的关键特性

    SQL Server 2016 CTP2.2 的关键特性 正如微软CEO 说的,SQL Server2016 是一个Breakthrough Flagship  Database(突破性的旗舰级数据库 ...

  6. 使用EntityFramework6连接MySql数据库(db first方式)

    准备工具: VS2013.MySQL For VisualStudio 1.1.4.Connector/Net 6.8.3(百度网盘里) 程序包管理器执行命令: Install-Package Ent ...

  7. Expert 诊断优化系列------------------语句调优三板斧

    前面三篇通过CPU.内存.磁盘三巨头,讲述了如何透过现在看本质,怎样定位服务器三巨头反映出的问题.为了方便阅读给出链接: SQL SERVER全面优化-------Expert for SQL Ser ...

  8. Using assembly writing algorithm programs

    This's my first version.The logic is simple, just the selection sort. I spent much time learning how ...

  9. 【PRINCE2是什么】PRINCE2认证之七大原则

    经过前几讲中关于PRINCE2六大要素,四大步骤及整体思维架构的学习,相信各位看官已经对于PRINCE2有了大概的了解,那我们今天的学习内容会正式进入到七大原则内容的分享. 我们先来回顾一下,PRIN ...

  10. iOS-----正则表达式

    摘要: 正则表达式在字符串检验和查找中用处很广,IOS中也有其支持的类. 正则表达式在iOS开发中的应用 正则表达式在字符串查找,替换,检测中的应用非常广泛,正则表达式是什么,有怎样的语法,可以参考我 ...