最近比较烦,深圳的工作还没着落,论文不想弄,烦。。。。。今天看了下jquery的数据缓存的代码,参考着Aaron的源码分析,自己有点理解了,和大家分享下。以后也打算把自己的jquery的学习心得写一个系列,当然和大神的源码分析是比不了的,只是自己在看的时候有好多地方是比较难理解的,为新手提供些便捷的学习方法,以后我会把我这些流水账整理成一个菜鸟学习jquery源码系列,现在就看到哪写到那,见谅。
内存泄露
首先看看什么是内存泄露,这里直接拿来Aaron中的这部分来说明什么是内存泄露,内存泄露的3种情况:
1 循环引用
2 Javascript闭包
3 DOM插入顺序
在这里我们只解释第一种情况,因为jquery的数据缓存就是解决这类的内存泄露的。一个DOM对象被一个Javascript对象引用,与此同时又引用同一个或其它的Javascript对象,这个DOM对象可能会引发内存泄漏。这个DOM对象的引用将不会在脚本停止的时候被垃圾回收器回收。要想破坏循环引用,引用DOM元素的对象或DOM对象的引用需要被赋值为null。
含有DOM对象的循环引用将导致大部分当前主流浏览器内存泄露
第一种:多个对象循环引用
var a=new Object; var b=new Object; a.r=b; b.r=a;
第二种:循环引用自己
var a=new Object; a.r=a;
循环引用很常见且大部分情况下是无害的,但当参与循环引用的对象中有DOM对象或者ActiveX对象时,循环引用将导致内存泄露。
我们把例子中的任何一个new Object替换成document.getElementById或者document.createElement就会发生内存泄露了。
在实际应用中我们要给我们的DOM添加数据,如果我们给一个DOM添加的数据太多的话,会存在循环引用的风险,例如我们添加的数据恰好引用了这个DOM元素,就会存在内存的泄露。所以jquery使用了数据缓存的机制就解决或者说避免这一问题。
数据缓存
$.cache 是jquery的缓存对象,这个是对象就是一个json,它的结构是这样的
{ "uid1": { // DOM节点1缓存数据,
"name1": value1,
"name2": value2
},
"uid2": { // DOM节点2缓存数据,
"name1": value1,
"name2": value2
}
数据缓存的接口是
$.data( element, key, value )
$(selector).data(key,value)
用法
看代码之前,先看看怎么使用jquery的数据缓存。在jquery中,有两个方法可以给对象设置数据,分别是实例方法$().data()和静态方法$.data(),具体的使用过程大家看api就知道了,这里简单介绍下
静态方法$.data()有三个参数,分别是挂在数据的元素,挂载的数据键,挂载数据的值,根据参数的不同,无非就是设置数据,取数据,具体如下
1 $.data( elem, key, value ) 在指定元素上存储/添加任意的数据,处理了循环引用和内存泄漏问题
2 $.data( elem, key ) 返回指定元素上name指定的值
3 $.data( elem ) 返回全部数据
4 $.data( elem,obj ) 在指定的元素上绑定obj
var obj = {}; $.data(obj , "a" , 1);//普通对象添加数据 console.log($.data(obj,"a"));//1 var dom = $("body");//dom添加数据 $.data(dom,"a",1) console.log($.data(dom,"a"));//1 $.data(obj , {"b":2});//两个参数 绑定数据对象 console.log($.data(dom,"b"));//2 console.log($.data(dom));//1 2
静态方法$().data()有两个参数,挂载的数据键,挂载数据的值
1 $(selector).data( key, value ) 在指定元素上存储/添加任意的数据,处理了循环引用和内存泄漏问题
2 $(selector).data( key ) 返回指定元素上name指定的值
3 $(selector).data(obj ) 在指定的元素上绑定obj
4 $(selector).data() 返回全部数据
$("body").data("a" , 1);//添加数据
console.log($("body").data("a"));//1
$("body").data({"b":2});//两个参数 绑定数据对象
console.log($("body").data("b"));//2
console.log($("body").data();//1 2
思路
回想下我们要解决什么问题:我们想在DOM上添加数据,但是不想引起内存的泄露,也就是我们不想引起循环引用,要尽量减少在DOM上挂数据。jquery的思路是这样:使用一个数据缓存对象$.cache,在需要绑定数据的DOM上扩展一个expando属性,这个属性存的是一个id,这里不会存在循环引用的情况了,之后将数据存在$.cache[id]上,当我们取DOM上的数据的时候,我们可以根据DOM上的expando找到id,进而找到存在$.cache[id]上的数据。可以看出jquery只是在DOM上扩展了一个属性expando,数据都存在了$.cache中,利用expando这个属性建立DOM和缓存对象之间的联系。无论我们添加多少的数据都会存储在缓存对象中,而不是直接挂在DOM上。这个唯一id是一个整型值,初始为0,调用data接口时自动加一,唯一id附加在以$.expando命名的属性上,$.expando是动态生成的,类似于一个时间戳,以尽可能的避免与用户变量冲突。从匹配的DOM元素上取到唯一id,在$.cache中找到唯一id对应的对象,再从对应的对象中找到key对应的值
看例子,在源码里打断点看一下
$.data($("body")[0],{"a":1});
console.log($.data($("body")[0],"a"));
DOM对象扩展了一个属性,这个属性存的是cache的id。
这样大家就比较明显了。
实现
expando就是一个类似时间戳的东东,源码
expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" )
就是为了生成标识的,没啥可说的。
这是静态方法的代码的整体结构,我看到的1.10.2,变化较大,所有的方法的实现都封装成了函数,主要看 internalData( elem, name, data )这个函数,其他的大伙自己看看吧
jQuery.extend({ cache: {}, // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. noData: { "applet": true, "embed": true, // Ban all objects except for Flash (which handle expandos) "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" }, hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return !!elem && !isEmptyDataObject( elem ); }, data: function( elem, name, data ) { return internalData( elem, name, data ); }, removeData: function( elem, name ) { return internalRemoveData( elem, name ); }, // For internal use only. _data: function( elem, name, data ) { return internalData( elem, name, data, true ); }, _removeData: function( elem, name ) { return internalRemoveData( elem, name, true ); }, // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { // Do not set data on non-element because it will not be cleared (#8335). if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { return false; } var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; // nodes accept data unless otherwise specified; rejection can be conditional return !noData || noData !== true && elem.getAttribute("classid") === noData; } });