-
原文地址:http://www.it165.net/pro/html/201404/11922.html
内存泄露
首先看看什么是内存泄露,这里直接拿来Aaron中的这部分来说明什么是内存泄露,内存泄露的3种情况:
1 循环引用
2 Javascript闭包
3 DOM插入顺序
在这里我们只解释第一种情况,因为jquery的数据缓存就是解决这类的内存泄露的。一个DOM对象被一个Javascript对象引用,与此同时又引用同一个或其它的Javascript对象,这个DOM对象可能会引发内存泄漏。这个DOM对象的引用将不会在脚本停止的时候被垃圾回收器回收。要想破坏循环引用,引用DOM元素的对象或DOM对象的引用需要被赋值为null。
含有DOM对象的循环引用将导致大部分当前主流浏览器内存泄露
第一种:多个对象循环引用
1.
var a=
new
Object;
2.
3.
var b=
new
Object;
4.
5.
a.r=b;
6.
7.
b.r=a;
第二种:循环引用自己
1.
var a=
new
Object;
2.
3.
a.r=a;
循环引用很常见且大部分情况下是无害的,但当参与循环引用的对象中有DOM对象或者ActiveX对象时,循环引用将导致内存泄露。
我们把例子中的任何一个new Object替换成document.getElementById或者document.createElement就会发生内存泄露了。
在实际应用中我们要给我们的DOM添加数据,如果我们给一个DOM添加的数据太多的话,会存在循环引用的风险,例如我们添加的数据恰好引用了这个DOM元素,就会存在内存的泄露。所以jquery使用了数据缓存的机制就解决或者说避免这一问题。
数据缓存
$.cache 是jquery的缓存对象,这个是对象就是一个json,它的结构是这样的
1.
{
'uid1'
: {
// DOM节点1缓存数据,
2.
'name1'
: value1,
3.
'name2'
: value2
4.
},
5.
'uid2'
: {
// DOM节点2缓存数据,
6.
'name1'
: value1,
7.
'name2'
: value2
8.
}
数据缓存的接口是
$.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 ) 在指定的元素上绑定obj01.
var obj = {};
02.
$.data(obj ,
'a'
,
1
);
//普通对象添加数据
03.
console.log($.data(obj,
'a'
));
//1
04.
var dom = $(
'body'
);
//dom添加数据
05.
$.data(dom,
'a'
,
1
)
06.
console.log($.data(dom,
'a'
));
//1
07.
$.data(obj , {
'b'
:
2
});
//两个参数 绑定数据对象
08.
console.log($.data(dom,
'b'
));
//2
09.
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() 返回全部数据1.
$(
'body'
).data(
'a'
,
1
);
//添加数据
2.
console.log($(
'body'
).data(
'a'
));
//1
3.
$(
'body'
).data({
'b'
:
2
});
//两个参数 绑定数据对象
4.
console.log($(
'body'
).data(
'b'
));
//2
5.
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对应的值
看例子,在源码里打断点看一下
1.
$.data($(
'body'
)[
0
],{
'a'
:
1
});
2.
console.log($.data($(
'body'
)[
0
],
'a'
));
DOM对象扩展了一个属性,这个属性存的是cache的id。
这样大家就比较明显了。
实现
expando就是一个类似时间戳的东东,源码
1.
expando:
'jQuery'
+ ( jQuery.fn.jquery + Math.random() ).replace( /D/g,
''
)
就是为了生成标识的,没啥可说的。
这是静态方法的代码的整体结构,我看到的1.10.2,变化较大,所有的方法的实现都封装成了函数,主要看 internalData( elem, name, data )这个函数,其他的大伙自己看看吧
01.
jQuery.extend({
02.
cache: {},
03.
04.
// The following elements throw uncatchable exceptions if you
05.
// attempt to add expando properties to them.
06.
noData: {
07.
'applet'
:
true
,
08.
'embed'
:
true
,
09.
// Ban all objects except for <a href="http://www.it165.net/design/wfl/" target="_blank" class="keylink">Flash</a> (which handle expandos)
10.
'object'
:
'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'
11.
},
12.
13.
hasData: function( elem ) {
14.
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
15.
return
!!elem && !isEmptyDataObject( elem );
16.
},
17.
18.
data: function( elem, name, data ) {
19.
return
internalData( elem, name, data );
20.
},
21.
22.
removeData: function( elem, name ) {
23.
return
internalRemoveData( elem, name );
24.
},
25.
26.
// For internal use only.
27.
_data: function( elem, name, data ) {
28.
return
internalData( elem, name, data,
true
);
29.
},
30.
31.
_removeData: function( elem, name ) {
32.
return
internalRemoveData( elem, name,
true
);
33.
},
34.
35.
// A method for determining if a DOM node can handle the data expando
36.
acceptData: function( elem ) {
37.
// Do not set data on non-element because it will not be cleared (#8335).
38.
if
( elem.nodeType && elem.nodeType !==
1
&& elem.nodeType !==
9
) {
39.
return
false
;
40.
}
41.
42.
var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
43.
44.
// nodes accept data unless otherwise specified; rejection can be conditional
45.
return
!noData || noData !==
true
&& elem.getAttribute(
'classid'
) === noData;
46.
}
47.
});
01.
function internalData( elem, name, data, pvt
/* Internal Use Only */
){
02.
if
( !jQuery.acceptData( elem ) ) {
//查看是否可以接受数据
03.
return
;
04.
}
05.
var ret, thisCache,
06.
internalKey = jQuery.expando,
//jQuery副本的唯一标识
07.
// We have to handle DOM nodes and JS objects differently because IE6-7
08.
// can't GC object references properly across the DOM-JS boundary
09.
isNode = elem.nodeType,
//判断DOM节点
10.
// Only DOM nodes need the global jQuery cache; JS object data is
11.
// attached directly to the object so GC can occur automatically
12.
cache = isNode ? jQuery.cache : elem,
//若是是DOM对象,则cache就是$.cache,否则为参数elem对象
13.
// Only defining an ID for JS objects if its cache already exists allows
14.
// the code to shortcut on the same path as a DOM node with no cache
15.
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
//找id,id可能在DOM[expando]中,也可以在elem[expando]中
16.
// Avoid doing any more work than we need to when trying to get data on an
17.
// object that has no data at all
18.
if
( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name ===
'string'
) {
19.
return
;
//参数的一些判断限制
20.
}
21.
if
( !id ) {
//id不存在
22.
// Only DOM nodes need a new unique ID for each element since their data
23.
// ends up in the global cache
24.
if
( isNode ) {
//是DOM节点
25.
id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;
//生成一个id
26.
}
else
{
//不是DOM,是一个对象
27.
id = internalKey;
//那么id就是那个expando
28.
}
29.
}
30.
if
( !cache[ id ] ) {
//cache中不存在数据,先弄成空的,一会在填充
31.
// Avoid exposing jQuery metadata on plain JS objects when the object
32.
// is serialized using JSON.stringify
33.
cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
34.
}
35.
// An object can be passed to jQuery.data instead of a key/value pair; this gets
36.
// shallow copied over onto the existing cache
37.
if
( typeof name ===
'object'
|| typeof name ===
'function'
) {
//处理第二个参数时对象或者是函数的情况
38.
if
( pvt ) {
//不太懂
39.
cache[ id ] = jQuery.extend( cache[ id ], name );
40.
}
else
{
//添加到data属性上
41.
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
42.
}
43.
}
44.
thisCache = cache[ id ];
45.
// jQuery data() is stored in a separate object inside the object's internal data
46.
// cache in order to avoid key collisions between internal data and user-defined
47.
// data.
48.
if
( !pvt ) {
49.
if
( !thisCache.data ) {
50.
thisCache.data = {};
51.
}
52.
thisCache = thisCache.data;
53.
}
54.
if
( data !== undefined ) {
//第三个参数存在,就是存数据
55.
thisCache[ jQuery.camelCase( name ) ] = data;
56.
}
57.
// Check for both converted-to-camel and non-converted data property names
58.
// If a data property was specified
59.
if
( typeof name ===
'string'
) {
60.
61.
// First Try to find as-is property data
62.
ret = thisCache[ name ];
//取出来待返回的那个value
63.
//有啥用 这么麻烦
64.
// Test for null|undefined property data
65.
if
( ret ==
null
) {
66.
// Try to find the camelCased property
67.
ret = thisCache[ jQuery.camelCase( name ) ];
68.
}
69.
}
else
{
70.
ret = thisCache;
//就是返回存进来的那个对象或者函数
71.
}
72.
return
ret;
73.
}
实现起来还是比较简单的,只是有些地方jquery考虑的太周全了,我等凡人看不太透彻。
pS:给DOM对象添加的数据是存储在了$.cache中,而给对象添加书数据直接挂在了对象的expando上面。其实给一个对象挂数据也没有什么实际的意义。
看源码可以知道,看个例子更明显
1.
var obj = {};
2.
$.data(obj,{
'a'
:
1
});
3.
console.log($.data(obj,
'a'
));
4.
console.log(obj);
结果:
实例方法data()其实就是调用了$.data()这个静态方法,这里就不说了。
01.
jQuery.fn.extend({
02.
data: function( key, value ) {
03.
var attrs, name,
04.
data =
null
,
05.
i =
0
,
06.
elem =
this
[
0
];
07.
08.
// Special expections of .data basically thwart jQuery.access,
09.
// so implement the relevant behavior ourselves
10.
11.
// Gets all values
12.
if
( key === undefined ) {
13.
if
(
this
.length ) {
14.
data = jQuery.data( elem );
15.
16.
if
( elem.nodeType ===
1
&& !jQuery._data( elem,
'parsedAttrs'
) ) {
17.
attrs = elem.attributes;
18.
for
( ; i < attrs.length; i++ ) {
19.
name = attrs[i].name;
20.
21.
if
( name.indexOf(
'data-'
) ===
0
) {
22.
name = jQuery.camelCase( name.slice(
5
) );
23.
24.
dataAttr( elem, name, data[ name ] );
25.
}
26.
}
27.
jQuery._data( elem,
'parsedAttrs'
,
true
);
28.
}
29.
}
30.
31.
return
data;
32.
}
33.
34.
// Sets multiple values
35.
if
( typeof key ===
'object'
) {
36.
return
this
.each(function() {
37.
jQuery.data(
this
, key );
38.
});
39.
}
40.
41.
return
arguments.length >
1
?
42.
43.
// Sets one value
44.
this
.each(function() {
45.
jQuery.data(
this
, key, value );
//这是重点
46.
}) :
47.
48.
// Gets one value
49.
// Try to fetch any internally stored data first
50.
elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) :
null
;
51.
},
问题
现在我们利用源码分析一些问题
01.
var a = $(
'body'
);
02.
var b = $(
'body'
);
03.
a.data(
'a'
,
1
);
04.
b.data(
'a'
,
2
);
05.
console.log(a.data(
'a'
));
//2
06.
console.log(b.data(
'a'
));
//2
07.
08.
$.data(a,
'b'
,
1
);
09.
$.data(b,
'b'
,
2
);
10.
console.log($.data(a,
'b'
))
//1
11.
console.log($.data(b,
'b'
))
//2
12.
13.
$.data(a[
0
],
'b'
,
1
);
14.
$.data(b[
0
],
'b'
,
2
);
15.
console.log($.data(a[
0
],
'b'
));
//2
16.
console.log($.data(b[
0
],
'b'
));
//2
看着有些晕,先看下这个
1.
var a = $(
'body'
);
2.
var b = $(
'body'
);
3.
console.log(a[
0
] == b[
0
]);
//true
4.
console.log(a == b);
//false
5.
console.log( $(
'body'
) == $(
'body'
));
//false
每一次$('body')都生成一个新的对象,所以每一次都会不同,$('body')[0]都是指向同一个body对象,a 和b指向的每个新对象的地址,所以不同。
看第一组
1.
var a = $(
'body'
);
2.
var b = $(
'body'
);
3.
a.data(
'a'
,
1
);
4.
b.data(
'a'
,
2
);
5.
console.log(a.data(
'a'
));
//2
6.
console.log(b.data(
'a'
));
//2
在看源代码这句
1.
this
.each(function() {
2.
jQuery.data(
this
, key, value );
3.
})
调用$.data(),但是这里第一个参数为this,是原生的DOM对象,第一组中的a和b的DOM对象都是body,所以添加数据会产生覆盖现象。
第二组和第二组是正常情况,不解释了。
小结
这就是我的理解,希望大家指正。以后会多分析jquery的实现过程,源码的细节太难了。
//