问题1:解决IE6-8中移除自定义事件导致的内存泄漏问题
jQuery.removeEvent = document.removeEventListener ?
function( elem, type, handle ) {
if ( elem.removeEventListener ) {
elem.removeEventListener( type, handle, false );
}
} :
//如果是IE浏览器那么也要移除事件,调用detachEvent方法
function( elem, type, handle ) {
var name = "on" + type;
if ( elem.detachEvent ) {
// #8545, #7054, preventing memory leaks for custom events in IE6-8
// detachEvent needed property on element, by name of that event, to properly expose it to GC
if ( typeof elem[ name ] === strundefined ) {//如果恒等于undefined,那么设置为null,移除引用
elem[ name ] = null;
}
elem.detachEvent( name, handle );
}
};
问题2:解决移除了元素以后,该元素的事件没有移除导致的内存泄漏之empty方法
empty: function() {
var elem,
i = 0;
for ( ; (elem = this[i]) != null; i++ ) {
// Remove element nodes and prevent memory leaks
if ( elem.nodeType === 1 ) {
//获取该元素下面的所有子元素,记住:是子元素
//然后清除这个DOM对象上面绑定
jQuery.cleanData( getAll( elem, false ) );
}
// Remove any remaining nodes
while ( elem.firstChild ) {
elem.removeChild( elem.firstChild );
}
// If this is a select, ensure that it displays empty (#12336)
// Support: IE<9
if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
elem.options.length = 0;
}
}
return this;
}
问题3:innerHTML导致的,移除元素以后但是该元素上面还有事件的问题
html: function( value ) {
return access( this, function( value ) {
var elem = this[ 0 ] || {},
i = 0,
l = this.length;
if ( value === undefined ) {
return elem.nodeType === 1 ?
elem.innerHTML.replace( rinlinejQuery, "" ) :
undefined;
}
// See if we can take a shortcut and just use innerHTML
if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
( support.htmlSerialize || !rnoshimcache.test( value ) ) &&
( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
!wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) {
value = value.replace( rxhtmlTag, "<$1></$2>" );
try {
for (; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
elem = this[i] || {};
if ( elem.nodeType === 1 ) {
jQuery.cleanData( getAll( elem, false ) );//调用innerHTML之前必须要移除元素本来具有的事件等
elem.innerHTML = value;
}
}
elem = 0;
// If using innerHTML throws an exception, use the fallback method
} catch(e) {}
}
if ( elem ) {
this.empty().append( value );
}
}, null, value, arguments.length );
}
问题4:当script已经load后我们移除事件引用,防止内存泄漏
(销毁闭包的方式)
script.onload = script.onreadystatechange = function( _, isAbort ) {
if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;//当脚本已经onload或者状态是loaded或者complete后要移除事件引用防止内存泄漏
// Remove the script
if ( script.parentNode ) {
script.parentNode.removeChild( script );
}
// Dereference the script
script = null;//解除引用,因为回调以后该script的DOM对象已经没有用处了
// Callback if not abort
if ( !isAbort ) {
callback( 200, "success" );
}
};
head.insertBefore( script, head.firstChild );//加入到DOM树中才开始下载script脚本!
如果我们只是需要外层引用的COM对象的属性,而不是整个对象(只要闭包不引用COM,那么就不会循环引用内存泄漏,因为COM引用次数为0)。
function assignHandler()
{
//如果闭包中间只是需要访问COM元素的id,那么我们就可以不让闭包访问COM元素,而只能访问id
var element= document.getElementById("myBtn");
//保存我们闭包中需要的id,而不保存COM,防止循环引用.因为COM采用引用计数,而js采用的标记清除!
var id=element.id;
element.οnclick=function()
{
console.log(id);//打印["myBtn"]
console.log(element);//因为闭包可以监测外部活动变量的引用变化,所以打印null!
}
//把element设置为null,可以解除对COM对象的引用,从而COM对象的引用次数为0了!
element=null;
}
如果我们需要引用外层的COM对象,而不是属性,那么我们可以参照jQuery方法:
function assignHandler()
{
//如果闭包需要访问外层的COM对象,而不是COM对象的属性
var element= document.getElementById("myBtn");
element.οnclick=function()
{
console.log(element);//调用的时候仍然可以访问外面的element对象
element.οnclick=null;//把onclick回调函数设置为null,那么click事件调用以后闭包会被销毁,从而无法引用COM对象
elemement=null;//如果click调用以后我们不需要对element进行继续操作,那么我们解除引用就行!
}
}
note:上面的方法无非出于两种意识:
第一,如果只是需要访问COM的属性,那么我们自己销毁COM,从而让闭包只能包含COM属性,因为COM属性不会导致循环引用,只有COM对象是循环引用;第二,如果需要访问整个COM对象,为了防止闭包对COM的循环引用,导致COM引用次数至少为1,那么我们调用后手动销毁闭包,同时如果COM不要重新利用,那么我们解除引用。
即拆分COM或者销毁闭包(设置为null或者函数绑定为一个新的函数句柄都行)
问题5:创建元素以后必须设置该元素为null,防止IE内存泄漏
(function() {
var div = document.createElement( "div" );
// Execute the test only if not already executed in another module.
if (support.deleteExpando == null) {
// Support: IE<9
support.deleteExpando = true;
try {
delete div.test;
} catch( e ) {
support.deleteExpando = false;
}
}
// Null elements to avoid leaks in IE.
div = null;//创建了该元素以后我们要把他的引用设置为null防止内存泄漏
})();
解除引用的方式来释放闭包外部函数的活动变量无法被销毁的情况:
function createCompare(propertyName)
{
return function(object1,object2)
{
var value1=object1[propertyName];
var value2=object2[propertyName];
if(value1<value2)
{
return -1;
}else if(value1>value2)
{
return 1;
}else
{
return 0;
}
}
}
var compare=createCompare("name");
//compare持有内部匿名函数的引用,如果返回的compare函数引用不被销毁那么外层函数的propertyName等
//活动对象将不会被销毁!
var result=compare({name:"qinliang"},{name:"klf"});
console.log(result);//打印1
compare=null;//这时候外部的函数的引用就可以被销毁了,这就是解除引用!
垃圾回收的策略:
(1)标记清除
当变量进入环境将这个变量标记为"进入环境",当变量离开环境是,将其标记为"离开环境"。垃圾收集器在运行的时候会给存储在内存中的所有变量加上标记;然后去除环境中的变量和被环境中的变量引用的变量的标记;而在此之后在被加上标记的变量被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了;垃圾收集器完成内存清理工作!(2008为止,IE,Safari,Opera,FireFox都是使用标记清除或者类似的策略,只不过时间间隔不相同)
(2)引用计数
出现循环引用问题(在标记清除中不存在,因为函数执行完毕后会离开作用域,所以会被销毁);IE中有一部分不是原生的javascript对象,如BOM和DOM中的对象是使用C++以Com对象的形式存在,而COM对象的垃圾收集机制就是引用计数策略,因此,即使IE的js引擎是使用标记清除策略,但javascript访问的COM仍然是基于引用计算策略的。也就是说,在IE中,只要存在COM对象,就会有循环引用问题(IE9之后已经把BOM和DOM对象都转化为真正的javascript对象了)。
//IE中的DOM是COM对象,所以存在循环引用,即使DOM从页面移除也不会被回收!
var elem=document.getElementById("s");
var jsObj=new Object();
jsObj.dom=elem;
elem.js=jsObj;
上面的js对象和DOM对象就存在循环引用,那么怎么解决呢?
//手动断开js对象和DOM对象之间的循环引用
jsObj.dom=null;
elem.js=null;
note:这就是jQuery中的问题4的解决办法,他也存在循环引用的情况,来源于js函数对象和DOM的script元素之间的循环引用。于是,
他当函数调用以后通过把οnlοad=onreadystatechange=null取消了循环引用。至于script=null只是为了解除引用,因为script回调以后没有任何作用,所以解除引用!
必须要弄清楚:只有COM对象是引用计数的,所以只要把COM对象的引用次数转化为0,那么引用计数导致的循环引用问题就会迎刃而解!所以解决方法就是onreadystate=οnlοad=null,只要销毁了闭包,那么就不会存在对COM对象的引用了。
管理内存:
function createPerson(name)
{
var person=new Object();
person.name=name;
return person;//调用函数以后会自动回收垃圾,因为离开了作用域,所以函数内部的变量不要手动释放内存!
}
var globalPerson=createPerson("qinliang");
//全局变量我们还是需要在不用的时候手动释放内存!
globalPerson=null;
note:优化内存占用的最佳的方式就是值保存必要的数据,一旦数据不能在用,最好将值设置为null来释放引用,也就是解除引用!这就是上面问题5的解决方法!
我们甚至可以手动触发垃圾收集程序,IE用window.collectGarbage,Opera7可以用window.opera.collect方法!