如何更好地优化 javascript 内存泄露的内存回收

前端性能优化指南 - 姚嘉鑫博客
var theme_base_args = {"admin_ajax":"http:\/\/\/wp-admin\/admin-ajax.php","ajax_comment":"1","comment_loading":"\u6b63\u\u4ea4...","ajax_load_page":"1","insert_smiley":"JavaScript的内存释放问题详解
本文详细的讲解了JavaScript及IE浏览器对内存的管理和释放的时机和方法,希望对前端开发人员有所帮助。
一个内存释放的实例&SCRIPT LANGUAGE="JavaScript"&
strTest = "1";
for ( var i = 0; i & 25; i ++ )
strTest += strT
alert(strTest);
delete strT
CollectGarbage();
&/SCRIPT&CollectGarbage,是IE的一个特有属性,用于释放内存的使用方法嘛应该是,将该变量或引用对象,设置为null或delete然后在进行释放动作在做CollectGarbage前,要必需清楚的两个必备条件:引用-&一个对象在其生存的上下文环境之外,即会失效。-&一个全局的对象在没有被执用(引用)的情况下,即会失效。//---------------------------------------------------------
// JavaScript对象何时失效
//---------------------------------------------------------
function testObject() {
var _obj1 = new Object();
function testObject2() {
var _obj2 = new Object();
return _obj2;
testObject();
testObject2()
var obj3 = testObject2();
var obj4 = testObject2();
var arr = [obj4];
arr = [];在这四个示例中:-&“示例1”在函数testObject()中构造了_obj1,但是在函数退出时,它就已经离开了函数的上下文环境,因此_obj1失效了;-&“示例2”中,testObject2()中也构造了一个对象_obj2并传出,因此对象有了“函数外”的上下文环境(和生存周期),然而由于函数的返回值没有被其它变量“持有”,因此_obj2也立即失效了;-&“示例3”中,testObject2()构造的_obj2被外部的变量obj3持用了,这时,直到“obj3=null”这行代码生效时,_obj2才会因为引用关系消失而失效。-&与示例3相同的原因,“示例4”中的_obj2会在“arr=[]”这行代码之后才会失效。但是,对象的“失效”并不等会“释放”。在JavaScript运行环境的内部,没有任何方式来确切地告诉用户“对象什么时候会释放”。这依赖于JavaScript的内存回收机制。——这种策略与.NET中的回收机制是类同的。在前面的Excel操作示例代码中,对象的所有者,也就是&EXCEL.EXE&这个进程只能在“ActiveX&Object实例的释放”之后才会发生。而文件的锁,以及操作系统的权限凭证是与进程相关的。因此如果对象仅是“失效”而不是“释放”,那么其它进程处理文件和引用操作系统的权限凭据时就会出问题。——有些人说这是JavaScript或者COM机制的BUG。其实不是,这是OS、IE和JavaScript之间的一种复杂关系所导致的,而非独立的问题。Microsoft公开了解决这种问题的策略:主动调用内存回收过程。在(微软的)JScript中提供了一个CollectGarbage()过程(通常简称GC过程),GC过程用于清理当前IE中的“失效的对象失例”,也就是调用对象的析构过程。在上例中调用GC过程的代码是://---------------------------------------------------------
// 处理ActiveX Object时,GC过程的标准调用方式
//---------------------------------------------------------
function writeXLS() {
excel.Quit();
setTimeout(CollectGarbage, 1);
}第一行代码调用excel.Quit()方法来使得excel进程中止并退出,这时由于JavaScript环境执有excel对象实例,因此excel进程并不实际中止。第二行代码使excel为null,以清除对象引用,从而使对象“失效”。然而由于对象仍旧在函数上下文环境中,因此如果直接调用GC过程,对象仍然不会被清理。第三行代码使用setTimeout()来调用CollectGarbage函数,时间间隔设为'1',只是使得GC过程发生在writeXLS()函数执行完之后。这样excel对象就满足了“能被GC清理”的两个条件:没有引用和离开上下文环境。GC过程的使用,在使用了ActiveX&Object的JS环境中很有效。一些潜在的ActiveXObject包括XML、VML、OWC(Office&Web&Componet)、flash,甚至包括在JS中的VBArray。从这一点来看,ajax架构由于采用了XMLHTTP,并且同时要满足“不切换页面”的特性,因此在适当的时候主动调用GC过程,会得到更好的效率用UI体验。事实上,即使使用GC过程,前面提到的excel问题仍然不会被完全解决。因为IE还缓存了权限凭据。使页的权限凭据被更新的唯一方法,只能是“切换到新的页面”,因此事实上在前面提到的那个SPS项目中,我采用的方法并不是GC,而是下面这一段代码://---------------------------------------------------------
// 处理ActiveX Object时采用的页面切换代码
//---------------------------------------------------------
function writeXLS() {
excel.Quit();
// 下面代码用于解决IE call Excel的一个BUG, MSDN中提供的方法:
// setTimeout(CollectGarbage, 1);
// 由于不能清除(或同步)网页的受信任状态, 所以将导致SaveAs()等方法在
// 下次调用时无效.
location.reload();
}delete&运算符在手册上的说明引用从对象中删除一个属性,或从数组中删除一个元素。delete&expressionexpression&参数是一个有效的&JScript&表达式,通常是一个属性名或数组元素。&说明如果&expression&的结果是一个对象,且在&expression&中指定的属性存在,而该对象又不允许它被删除,则返回&false。在所有其他情况下,返回&true。最后之最后,关于GC的一个补充说明:在IE窗体被最小化时,IE将会主动调用一次CollectGarbage()函数。这使得IE窗口在最小化之后,内存占用会有明显改善
您可能还关注以下内容:
点击:6314&&&&
点击:6376&&&&
点击:4148&&&&
点击:12378&&&&
点击:5222&&&&
点击:8723&&&&
点击:4159&&&&
点击:5295&&&&
点击:2384&&&&
点击:16328&&&&
点击:15898&&&&
点击:14405&&&&
点击:12570&&&&
点击:12378&&&&
点击:8758&&&&
点击:8853&&&&
点击:13738&&&&
点击:12810&&&&
合作网站:JavaScript内存管理 - 简书
JavaScript内存管理
作为一门高级语言,JS并不像低级语言C/C++那样拥有对内存的完全掌控。JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题,但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况。
内存的生命周期
JS环境中分配的内存一般有如下生命周期:
内存分配:当我们申明变量、函数、对象的时候,系统会自动为他们分配内存
内存使用:即读写内存,也就是使用变量、函数等
内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
内存分配的几个例子:
// 为变量分配内存
var i = 11;
var s = "ifcode";
// 为对象分配内存
var person = {
name: 'ifcode'
// 为函数分配内存
function sum(a, b) {
return a +
垃圾回收算法
对垃圾回收算法来说,核心思想就是如何判断内存已经不再使用了。下面介绍两种常见浏览器的垃圾回收算法。
引用计数算法
熟悉C语言的同学的都明白,引用无非就是指向某一物体的指针。对不熟悉底层语言的同学来说,可简单将引用视为一个对象访问另一个对象的路径。(这里的对象是一个宽泛的概念,泛指JS环境中的实体)。
引用计数算法定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。如果没有其他对象指向它了,说明该对象已经不再需了。
下面来看个例子:
// 创建一个对象person,他有两个指向属性age和name的引用
var person = {
name: 'ifcode'
person.name = // 虽然设置为null,但因为person对象还有指向name的引用,因此name不会回收
person = 1;
//原来的person对象被赋值为1,但因为有新引用p指向原person对象,因此它不会被回收
//原person对象已经没有引用,很快会被回收
由上面可以看出,引用计数算法是个简单有效的算法。但它却存在一个致命的问题:循环引用。如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
function cycle() {
var o1 = {};
var o2 = {};
o1.a = o2;
o2.a = o1;
return "Cycle reference!"
上面我们申明了一个cycle方程,其中包含两个相互引用的对象。在调用函数结束后,对象o1和o2实际上已离开函数范围,因此不再需要了。但根据引用计数的原则,他们之间的相互引用依然存在,因此这部分内存不会被回收,内存泄露不可避免了。
正是因为有这个严重的缺点,这个算法在现代浏览器中已经被下面要介绍的标记清除算法所取代了。但绝不可认为该问题已经不再存在了,因为还占有大量市场的IE6、IE7使用的正是这一算法。在需要照顾兼容性的时候,某些看起来非常普通的写法也可能造成意想不到的问题:
var div = document.createElement("div");
div.onclick = function() {
console.log("click");
上面这种JS写法再普通不过了,创建一个DOM元素并绑定一个点击事件。那么这里有什么问题呢?请注意,变量div有事件处理函数的引用,同时事件处理函数也有div的引用!(div变量可在函数内被访问)。一个循序引用出现了,按上面所讲的算法,该部分内存无可避免地泄露哦了。
现在你明白为啥前端程序员都讨厌IE6了吧?拥有超多BUG并依然占有大量市场的IE6是前端开发一生之敌!亲,没有买卖就没有杀害。为了让你身边的前端程序员活得健康一些,请今天就升级你的浏览器吧!
标记清除算法
上面说过,现代的浏览器已经不再使用引用计数算法了。现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
标记清除算法将“不再使用的对象”定义为“无法达到的对象”。简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
从这个概念可以看出,无法触及的对象包含了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)。但反之未必成立。
根据这个概念,上面的例子可以正确被垃圾回收处理了。当div与其时间处理函数不能再从全局对象出发触及的时候,垃圾回收器就会标记并回收这两个对象。
如何写出对内存管理友好的JS代码?
如果还需要兼容老旧浏览器,那么就需要注意代码中的循环引用问题。或者直接采用保证兼容性的库来帮助优化代码。
对现代浏览器来说,唯一要注意的就是明确切断需要回收的对象与根部的联系。有时候这种联系并不明显,且因为标记清除算法的强壮性,这个问题较少出现。最常见的内存泄露一般都与DOM元素绑定有关:
email.message = document.createElement(“div”);
displayList.appendChild(email.message);
// 稍后从displayList中清除DOM元素
displayList.removeAllChildren();
div元素已经从DOM树中清除,也就是说从DOM树的根部无法触及该div元素了。但是请注意,div元素同时也绑定了email对象。所以只要email对象还存在,该div元素将一直保存在内存中。
如果你的引用只包含少量JS交互,那么内存管理不会对你造成太多困扰。一旦你开始构建中大规模的SPA或是服务器和桌面端的应用,那么就应当将内存泄露提上日程了。不要满足于写出能运行的程序,也不要认为机器的升级就能解决一切。当你从初级程序员走向资深的时候,关注细节真是你能脱颖而出的优点。
Live fast, die young.

我要回帖

更多关于 javascript优化 的文章

 

随机推荐