理解並解決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. 這樣的扯淡建議!

發佈了49 篇原創文章 · 獲贊 4 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章