细数jQuery源码中那些解决内存泄漏问题

问题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方法!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值