鸟食轩

 Microsoft .NET[C#] MVP 2003
随笔 - 429, 文章 - 235, 评论 - 5527, 引用 - 356
数据加载中……

理解并解决IE的内存泄漏方式[翻译2]

    大家节日快乐!俺就继续这个IE内存泄漏的主题来作为节日礼物了,并且相当欢迎大家来一起讨论。这一节讲Closures引起的内存泄漏,最后我还是决定把Closures翻译成了闭包或闭包函数。而且又在KB中看到一个对Closures的解释,它是这么说的:

<HTML>
<HEAD>
<script language="javascript">
function initpage()
{
    window.setTimeout(
"window.location.reload()"500"javascript");
}

</script>
</HEAD>
<body onload="initpage()" >
<div class='menu' id='menu'></div>
<script language='javascript'>
hookup(document.getElementById('menu'));
function hookup(element)
{
    element.attachEvent( 
"onmouseover", mouse);
    
function mouse () 
    
{
    }

}

</script>
</body>
</HTML>

    In this code, the handler (the mouse function) is nested inside the attacher (the hookup function). This arrangement means that the handler is closed over the scope of the caller (this arrangement is named a "closure").


闭包函数(Closures)

    由于闭包函数会使程序员在不知不觉中创建出循环引用,所以它对资源泄漏常常有着不可推卸的责任。而在闭包函数自己被释放前,我们很难判断父函数的参数以及它的局部变量是否能被释放。实际上闭包函数的使用已经很普通,以致人们频繁的遇到这类问题时我们却束手无策。在详细了解了闭包背后的问题和一些特殊的闭包泄漏示例后,我们将结合循环引用的图示找到闭包的所在,并找出这些不受欢迎的引用来至何处。

    CircularReferences.gif
    Figure 2. 闭包函数引起的循环引用

    普通的循环引用,是两个不可探知的对象相互引用造成的,但是闭包却不同。代替直接造成引用,闭包函数则取而代之从其父函数作用域中引入信息。通常,函数的局部变量和参数只能在该被调函数自身的生命周期里使用。当存在闭包函数后,这些变量和参数的引用会和闭包函数一起存在,但由于闭包函数可以超越其父函数的生命周期而存在,所以父函数中的局部变量和参数也仍然能被访问。在下面的示例中,参数1将在函数调用终止时正常被释放。当我们加入了一个闭包函数后,一个额外的引用产生,并且这个引用在闭包函数释放前都不会被释放。如果你碰巧将闭包函数放入了事件之中,那么你不得不手动从那个事件中将其移出。如果你把闭包函数作为了一个expando属性,那么你也需要通过置null将其清除。

    同时闭包会在每次调用中创建,也就是说当你调用包含闭包的函数两次,你将得到两个独立的闭包,而且每个闭包都分别拥有对参数的引用。由于这些显而易见的因素,闭包确实非常用以带来泄漏。下面的示例将展示使用闭包的主要泄漏因素:

<html>
    
<head>
        
<script language="JScript">

        
function AttachEvents(element)
        
{
            
// This structure causes element to ref ClickEventHandler
            element.attachEvent("onclick", ClickEventHandler);

            
function ClickEventHandler()
            
{
                
// This closure refs element
            }

        }


        
function SetupLeak()
        
{
            
// The leak happens all at once
            AttachEvents(document.getElementById("LeakedDiv"));
        }


        
function BreakLeak()
        
{
        }

        
</script>
    
</head>
    
<body onload="SetupLeak()" onunload="BreakLeak()">
        
<div id="LeakedDiv"></div>
    
</body>
</html>


    如果你对怎么避免这类泄漏感到疑惑,我将告诉你处理它并不像处理普通循环引用那么简单。"闭包"被看作函数作用域中的一个临时对象。一旦函数执行退出,你将失去对闭包本身的引用,那么你将怎样去调用detachEvent方法来清除引用呢?在Scott Isaacs的MSN Spaces上有一种解决这个问题的有趣方法。这个方法使用一个额外的引用(原文叫second closure,可是这个示例里致始致终只有一个closure)协助window对象执行onUnload事件,由于这个额外的引用和闭包的引用存在于同一个对象域中,于是我们可以借助它来释放事件引用,从而完成引用移除。为了简单起见我们将闭包的引用暂存在一个expando属性中,下面的示例将向你演示释放事件引用和清除expando属性。

<html>
    
<head>
        
<script language="JScript">

        
function AttachEvents(element)
        
{
            
// In order to remove this we need to put
            // it somewhere. Creates another ref
            element.expandoClick = ClickEventHandler;

            
// This structure causes element to ref ClickEventHandler
            element.attachEvent("onclick", element.expandoClick);

            
function ClickEventHandler()
            
{
                
// This closure refs element
            }

        }


        
function SetupLeak()
        
{
            
// The leak happens all at once
            AttachEvents(document.getElementById("LeakedDiv"));
        }


        
function BreakLeak()
        
{
            document.getElementById(
"LeakedDiv").detachEvent("onclick",
                document.getElementById(
"LeakedDiv").expandoClick);
            document.getElementById(
"LeakedDiv").expandoClick = null;
        }

        
</script>
    
</head>
    
<body onload="SetupLeak()" onunload="BreakLeak()">
        
<div id="LeakedDiv"></div>
    
</body>
</html>


    在这篇KB文章中,实际上建议我们除非迫不得已尽量不要创建使用闭包。文章中的示例,给我们演示了非闭包的事件引用方式,即把闭包函数放到页面的全局作用域中。当闭包函数成为普通函数后,它将不再继承其父函数的参数和局部变量,所以我们也就不用担心基于闭包的循环引用了。在非必要的时候不使用闭包这样的编程方式可以尽量使我们的代码避免这样的问题。

    最后,脚本引擎开发组的Eric Lippert,给我们带来了一篇关于闭包使用通俗易懂的好文章。他的最终建议也是希望在真正必要的时候才使用闭包函数。虽然他的文章没有提及闭包会使用的真正场景,但是这儿已有的大量示例非常有助于大家起步。

    to be continued ...


    不得不说Eric Lippert同志疾呼的:Don't use closures unless you really need closure semantics. In most cases, non-nested functions are the right way to go. 是非常消极的应付之辞。今天关于IE内存泄漏的文章已有很多,而且很大部分就是微软的自己人在解释,甚至象Eric Lippert这样引擎开发组的成员。但是他们的文章始终没有正面承认其实这就是IE的bug,而且是非常严重的bug,这事情其实完全不关脚本引擎对象和DOM对象的事。就是微软对产品不负责任的表现,不说IE4,1997那个春天那是太遥远了点,但是IE6也是2001年随xp发布的。使用COM可以给他们的开发带来很多便利,当然也利用很多现成的东西,可是居然在带来这样的严重问题后,他们却把大部分责任归咎于不合理和不正确的使用Closures技术!对于循环引用产生Memory Leak我根本就是不好意思提了,那样的问题是应该发生的吗?那就让我们拭目以待看IE7怎么解决这堆shit问题吧,当然我希望不要再看到类似:Do not use closures unless they are necessary. 这样的扯淡建议!

posted on 2006-06-01 21:55 birdshome 阅读(10906) 评论(10)  编辑 收藏 网摘 所属分类: Jscript&Dhtml开发

评论

#1楼    回复  引用    

微软自己Atlas 就是大量使用了闭包函数啊
2006-06-02 08:18 | windaa [未注册用户]

#2楼    回复  引用  查看    

有的地方是闭包含数,有的地方又是闭包函数
2006-06-02 08:50 | Icebird      

#3楼 [楼主]   回复  引用  查看    

@windaa
这个东西不是不能用,而是使用起来非常危险,搞得比C/C++自己分配释放内存还郁闷,因为它的表现是很不确定的。

@Icebird
已改正,谢谢:)
2006-06-02 09:57 | birdshome      

#4楼    回复  引用  查看    

翻译应该把原文做一个连接吧
2006-06-14 20:23 | ttyp      

#5楼 [楼主]   回复  引用  查看    

@ttyp
第一篇里有的。。。
2006-06-14 21:25 | birdshome      

#6楼    回复  引用    

全世界web开发者联合起来,共同抵制IE,强烈要求微软限期改掉内存泄露BUG……

#7楼    回复  引用  查看    

最近IE老是死掉,估计也是我的Closures太多了。。。
再读一遍,受益匪浅。
2006-09-01 09:15 | 浪子      

#8楼    回复  引用  查看    

birdshome的选择的皮胖不支持FF啊.
2006-09-25 13:36 | 阿不      

#9楼    回复  引用    

其实不用对闭包那么恐惧.....
实际上闭包的问题和对象的问题本质上是一样的
甚至可以把闭包看作特殊的“对象”

再看下面的例子:
function A()
{
var a = document.getElementById("some_ele");
return function(){alert(1);}
}
var a = A();
有人认为这种闭包一定会导致内存泄漏
及时你返会的闭包中没有引用a,但是因为闭包的缘故导致a不能被销毁

然而这种说法严格来说不完全准确
实际上上面这种用法对内存的耗费情况和下面
function A()
{
var a = document.getElementById("some_ele");
}
var a = new A()

完全一样
前者是因为调用对象不能被销毁
而后者是因为实际对象不能被销毁,它们的本质完全相同

a = null;
CollectBarbage();
都能释放它们
2007-08-14 18:04 | akira [未注册用户]

#10楼    回复  引用    

一开始使用闭包的时候,还不知道这是闭包的用法,后来慢慢的知道了这就是闭包,这时开始发现闭包,可能会出现循环引用,但当时还不知道会有内存泄露,后来一段时间频繁使用闭包,又对javascript有了深入了解后,发现闭包会导致内存泄露,一个让我痛苦的成长,我曾花大量时间对程序进行改良手动进行内存释放,但仍有大量对象无法释放.感谢博主翻译这篇文章.
2008-04-12 17:23 | sunyujia [未注册用户]

发表评论



姓名 [登录] [注册] 
主页
Email (仅博主可见) 
验证码 *  验证码看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论   新用户注册   返回页首      

导航: 网站首页 社区 新闻 博问 闪存 网摘 招聘 .NET频道 知识库 找找看 Google站内搜索



China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
China-Pub 计算机绝版图书按需印刷服务

相关文章:

相关链接:

历史上的今天:
2005-06-01 彻底清除狗太阳的3721病毒完全手册!