Js內(nèi)存泄漏及解決方案_第1頁
Js內(nèi)存泄漏及解決方案_第2頁
Js內(nèi)存泄漏及解決方案_第3頁
Js內(nèi)存泄漏及解決方案_第4頁
Js內(nèi)存泄漏及解決方案_第5頁
已閱讀5頁,還剩7頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領

文檔簡介

1、Js內(nèi)存泄漏及解決方案在IE下的JS編程中,以下的編程方式都會造成即使關閉IE也無法釋放內(nèi)存的問題,下面分類給出: 1、給DOM對象添加的屬性是一個對象的引用。范例: var MyObject = ; document.getElementById(myDiv).myProp = MyObject; 解決方法:在window.onunload事件中寫上: document.getElementById(myDiv).myProp = null; 2、DOM對象與JS對象相互引用。范例: function Encapsulator(element) this.elementReference =

2、 element; element.myProp = this; new Encapsulator(document.getElementById(myDiv); 解決方法:在onunload事件中寫上: document.getElementById(myDiv).myProp = null; 3、給DOM對象用attachEvent綁定事件。范例: function doClick() element.attachEvent(onclick, doClick); 解決方法:在onunload事件中寫上: element.detachEvent(onclick, doClick); 4、從外

3、到內(nèi)執(zhí)行appendChild。這時即使調(diào)用removeChild也無法釋放。范例: var parentDiv = document.createElement(div); var childDiv = document.createElement(div); document.body.appendChild(parentDiv); parentDiv.appendChild(childDiv); 解決方法:從內(nèi)到外執(zhí)行appendChild: var parentDiv = document.createElement(div); var childDiv = document.crea

4、teElement(div); parentDiv.appendChild(childDiv); document.body.appendChild(parentDiv); 5、反復重寫同一個屬性會造成內(nèi)存大量占用(但關閉IE后內(nèi)存會被釋放)。范例: for(i = 0; i 5000; i+) hostElement.text = asdfasdfasdf; 這種方式相當于定義了5000個屬性!解決方法:其實沒什么解決方法就是編程的時候盡量避免出現(xiàn)這種情況咯 說明: 1、以上資料均來源于微軟官方的MSDN站點,鏈接地址: 大家可以到上面這個地址中看到詳細的說明,包括范例和圖例都有。只是我英文

5、不太好,看不太懂,如果我上述有失誤或有需要補充的地方請大家指出。 2、對于第一條,事實上包括 element.onclick = funcRef 這種寫法也算在其中,因為這也是一個對對象的引用。在頁面onunload時應該釋放掉。 3、對于第三條,在MSDN的英文說明中好像是說即使調(diào)用detachEvent也無法釋放內(nèi)存,因為在attachEvent的時候就已經(jīng)造成內(nèi)存“LEAK”了,不過detachEvent后情況還是會好一點。不知道是不是這樣,請英文好的親能夠指出。 4、在實際編程中,這些內(nèi)存問題的實際影響并不大,尤其是給客戶使用時,客戶對此絕不會有察覺,然而這些問題對于程序員來說卻始終是

6、個心病 - 有這樣的BUG心里總會覺得不舒服吧?能解決則給與解決,這樣是最好的。事實上我在這樣頂級的JS源碼站點中,在它們的源碼里都會看到采用上述解決方式進行內(nèi)存的釋放管理。 理解并解決IE的內(nèi)存泄漏方式 Web開發(fā)的發(fā)展在過去一些的時候,Web開發(fā)人員并沒有太多的去關注內(nèi)存泄露問題。那時的頁面間聯(lián)系大都比較簡單,并主要使用不同的連接地址在同一個站點中導航,這樣的設計方式是非常有利于瀏覽器釋放資源的。即使Web頁面運行中真的出現(xiàn)了資源泄漏,那它的影響也是非常有限而且常常是不會被人在意的。今天人們對Web應用有了高更的要求。一個頁面很可能數(shù)小時不會發(fā)生URL跳轉(zhuǎn),并同時通過Web服務動態(tài)的更新頁

7、面內(nèi)容。復雜的事件關聯(lián)設計、基于對象的JScript和DHTML技術(shù)的廣泛采用,使得代碼的能力達到了其承受的極限。在這樣的情況和改變下,弄清楚內(nèi)存泄露方式變得非常的急迫,特別是過去這些問題都被傳統(tǒng)的頁面導航方法給屏蔽了。還算好的事情是,當你明確了希望尋找什么時,內(nèi)存泄露方式是比較容易被確定的。大多數(shù)你能遇到的泄露問題我們都已經(jīng)知道,你只需要少量額外的工作就會給你帶來好處。雖然在一些頁面中少量的小泄漏問題仍會發(fā)生,但是主要的問題還是很容易解決的。泄露方式在接下來的內(nèi)容中,我們會討論內(nèi)存泄露方式,并為每種方式給出示例。其中一個重要的示例是JScript中的Closure技術(shù),另一個示例是在事件執(zhí)行

8、中使用Closures。當你熟悉本示例后,你就能找出并修改你已有的大多數(shù)內(nèi)存泄漏問題,但是其它Closure相關的問題可能又會被忽視?,F(xiàn)在讓我們來看看這些個方式都有什么: 1、循環(huán)引用(Circular References) IE瀏覽器的COM組件產(chǎn)生的對象實例和網(wǎng)頁腳本引擎產(chǎn)生的對象實例相互引用,就會造成內(nèi)存泄漏。這也是Web頁面中我們遇到的最常見和主要的泄漏方式; 2、內(nèi)部函數(shù)引用(Closures) Closures可以看成是目前引起大量問題的循環(huán)應用的一種特殊形式。由于依賴指定的關鍵字和語法結(jié)構(gòu), Closures調(diào)用是比較容易被我們發(fā)現(xiàn)的; 3、頁面交叉泄漏(Cross-Page

9、Leaks) 頁面交叉泄漏其實是一種較小的泄漏,它通常在你瀏覽過程中,由于內(nèi)部對象薄計引起。下面我們會討論DOM插入順序的問題,在那個示例中你會發(fā)現(xiàn)只需要改動少量的代碼,我們就可以避免對象薄計對對象構(gòu)建帶來的影響; 4、貌似泄漏(Pseudo-Leaks) 這個不是真正的意義上的泄漏,不過如果你不了解它,你可能會在你的可用內(nèi)存資源變得越來越少的時候極度郁悶。為了演示這個問題,我們將通過重寫Script元素中的內(nèi)容來引發(fā)大量內(nèi)存的泄漏。循環(huán)引用循環(huán)引用基本上是所有泄漏的始作俑者。通常情況下,腳本引擎通過垃圾收集器(GC)來處理循環(huán)引用,但是某些未知因數(shù)可能會妨礙從其環(huán)境中釋放資源。對于IE來說,

10、某些DOM對象實例的狀態(tài)是腳本無法得知的。下面是它們的基本原則: Figure 1: 基本的循環(huán)引用模型本模型中引起的泄漏問題基于COM的引用計數(shù)。腳本引擎對象會維持對DOM對象的引用,并在清理和釋放DOM對象指針前等待所有引用的移除。在我們的示例中,我們的腳本引擎對象上有兩個引用:腳本引擎作用域和DOM對象的expando屬性。當終止腳本引擎時第一個引用會釋放,DOM 對象引用由于在等待腳本擎的釋放而并不會被釋放。你可能會認為檢測并修復假設的這類問題會非常的容易,但事實上這樣基本的的示例只是冰山一角。你可能會在30個對象鏈的末尾發(fā)生循環(huán)引用,這樣的問題排查起來將會是一場噩夢。如果你仍不清楚這

11、種泄漏方式在HTML代碼里到底怎樣,你可以通過一個全局腳本變量和一個DOM對象來引發(fā)并展現(xiàn)它。 - var myGlobalObject; function SetupLeak() / First set up the script scope to element reference myGlobalObject = document.getElementById(LeakedDiv); / Next set up the element to script scope reference document.getElementById(LeakedDiv).expandoProperty

12、= myGlobalObject; function BreakLeak() document.getElementById(LeakedDiv).expandoProperty = null; 你可以使用直接賦null值得方式來破壞該泄漏情形。在頁面文檔卸載前賦null值,將會讓腳本引擎知道對象間的引用鏈沒 有了?,F(xiàn)在它將能正常的清理引用并釋放DOM對象。在這個示例中,作為Web開發(fā)員的你因該更多的了解了對象間的關系。 作為一個基本的情形,循環(huán)引用可能還有更多不同的復雜表現(xiàn)。對基于對象的JScript,一個通常用法是通過封裝JScript對象來擴充DOM對 象。在構(gòu)建過程中,你常常會把DOM

13、對象的引用放入JScript對象中,同時在DOM對象中也存放上對新近創(chuàng)建的JScript對象的引用。你的這種應 用模式將非常便于兩個對象之間的相互訪問。這是一個非常直接的循環(huán)引用問題,但是由于使用不用的語法形式可能并不會讓你在意。要破環(huán) 這種使用情景可能變得更加復雜,當然你同樣可以使用簡單的示例以便于清楚的討論。 function Encapsulator(element) / Set up our element this.elementReference = element; / Make our circular reference element.expandoProperty = t

14、his; function SetupLeak() / The leak happens all at once new Encapsulator(document.getElementById(LeakedDiv); function BreakLeak() document.getElementById(LeakedDiv).expandoProperty = null; 更復雜的辦法還有記錄所有需要解除引用的對象和屬性,然后在Web文檔卸載的時候統(tǒng)一清理,但大多數(shù)時候你可能會再造成額外的泄漏情形,而并沒有解決你的問題。閉包函數(shù)(Closures)由于閉包函數(shù)會使程序員在不知不覺中創(chuàng)建出循

15、環(huán)引用,所以它對資源泄漏常常有著不可推卸的責任。而在閉包函數(shù)自己被釋放前,我們很難判斷父函數(shù)的參數(shù)以及它的局部變量是否能被釋放。實際上閉包函數(shù)的使用已經(jīng)很普通,以致人們頻繁的遇到這類問題時我們卻束手無策。在詳細了解了閉包背后的問題和一些特殊的閉包泄漏示例后,我們將結(jié)合循環(huán)引用的圖示找到閉包的所在,并找出這些不受歡迎的引用來至何處。Figure 2. 閉包函數(shù)引起的循環(huán)引用普通的循環(huán)引用,是兩個不可探知的對象相互引用造成的,但是閉包卻不同。代替直接造成引用,閉包函數(shù)則取而代之從其父函數(shù)作用域中引入信息。通常,函數(shù)的局部變量和參數(shù)只能在該被調(diào)函數(shù)自身的生命周期里使用。當存在閉包函數(shù)后,這些變量和參

16、數(shù)的引用會和閉包函數(shù)一起存在,但由于閉包函數(shù)可以超越其父函數(shù)的生命周期而存在,所以父函數(shù)中的局部變量和參數(shù)也仍然能被訪問。在下面的示例中,參數(shù)1將在函數(shù)調(diào)用終止時正常被釋放。當我們加入了一個閉包函數(shù)后,一個額外的引用產(chǎn)生,并且這個引用在閉包函數(shù)釋放前都不會被釋放。如果你碰巧將閉包函數(shù)放入了事件之中,那么你不得不手動從那個事件中將其移出。如果你把閉包函數(shù)作為了一個expando屬性,那么你也需要通過置null將其清除。同時閉包會在每次調(diào)用中創(chuàng)建,也就是說當你調(diào)用包含閉包的函數(shù)兩次,你將得到兩個獨立的閉包,而且每個閉包都分別擁有對參數(shù)的引用。由于這些顯而易見的因素,閉包確實非常用以帶來泄漏。下面的

17、示例將展示使用閉包的主要泄漏因素: 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

18、(LeakedDiv); function BreakLeak() 如果你對怎么避免這類泄漏感到疑惑,我將告訴你處理它并不像處理普通循環(huán)引用那么簡單。閉包被看作函數(shù)作用域中的一個臨時對象。一旦函數(shù)執(zhí)行退出,你將失去對閉包本身的引用,那么你將怎樣去調(diào)用detachEvent方法來清除引用呢?在Scott Isaacs的MSN Spaces上有一種解決這個問題的有趣方法。這個方法使用一個額外的引用(原文叫second closure,可是這個示例里致始致終只有一個closure)協(xié)助window對象執(zhí)行onUnload事件,由于這個額外的引用和閉包的引用存在于同一個對象域中,于是我們可以借助它來釋

19、放事件引用,從而完成引用移除。為了簡單起見我們將閉包的引用暫存在一個expando屬性中,下面的示例將向你演示釋放事件引用和清除expando屬性。 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.attachEven

20、t(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(LeakedD

21、iv).expandoClick); document.getElementById(LeakedDiv).expandoClick = null; 在這篇KB文章中,實際上建議我們除非迫不得已盡量不要創(chuàng)建使用閉包。文章中的示例,給我們演示了非閉包的事件引用方式,即把閉包函數(shù)放到頁面的全局作用域中。當閉包函數(shù)成為普通函數(shù)后,它將不再繼承其父函數(shù)的參數(shù)和局部變量,所以我們也就不用擔心基于閉包的循環(huán)引用了。在非必要的時候不使用閉包這樣的編程方式可以盡量使我們的代碼避免這樣的問題。最后,腳本引擎開發(fā)組的Eric Lippert,給我們帶來了一篇關于閉包使用通俗易懂的好文章。他的最終建議也是希望在真正

22、必要的時候才使用閉包函數(shù)。雖然他的文章沒有提及閉包會使用的真正場景,但是這兒已有的大量示例非常有助于大家起步。頁面交叉泄漏(Cross-Page Leaks)這種基于插入順序而常常引起的泄漏問題,主要是由于對象創(chuàng)建過程中的臨時對象未能被及時清理和釋放造成的。它一般在動態(tài)創(chuàng)建頁面元素,并將其添加到頁面DOM中時發(fā)生。一個最簡單的示例場景是我們動態(tài)創(chuàng)建兩個對象,并創(chuàng)建一個子元素和父元素間的臨時域(譯者注:這里的域(Scope)應該是指管理元素之間層次結(jié)構(gòu)關系的對象)。然后,當你將這兩個父子結(jié)構(gòu)元素構(gòu)成的的樹添加到頁面DOM樹中時,這兩個元素將會繼承頁面DOM中的層次管理域?qū)ο?,并泄漏之前?chuàng)建的那個

23、臨時域?qū)ο蟆O旅娴膱D示示例了兩種動態(tài)創(chuàng)建并添加元素到頁面DOM中的方法。在第一種方法中,我們將每個子元素添加到它的直接父元素中,最后再將創(chuàng)建好的整棵子樹添加到頁面DOM中。當一些相關條件合適時,這種方法將會由于臨時對象問題引起泄漏。在第二種方法中,我們自頂向下創(chuàng)建動態(tài)元素,并使它們被創(chuàng)建后立即加入到頁面DOM結(jié)構(gòu)中去。由于每個被加入的元素繼承了頁面DOM中的結(jié)構(gòu)域?qū)ο?,我們不需要?chuàng)建任何的臨時域。這是避免潛在內(nèi)存泄漏發(fā)生的好方法。 Figure 3. DOM插入順序泄漏模型 接下來,我們將給出一個躲避了大多數(shù)泄漏檢測算法的泄漏示例。因為我們實際上沒有泄漏任何可見的元素,并且由于被泄漏的對象太小

24、從而你可能根本不會注意這個問題。為了使我們的示例產(chǎn)生泄漏,在動態(tài)創(chuàng)建的元素結(jié)構(gòu)中將不得不內(nèi)聯(lián)的包含一個腳本函數(shù)指針。在我們設置好這些元素間的相互隸屬關系后這將會使我們泄漏內(nèi)部臨時腳本對象。由于這個泄漏很小,我們不得不將示例執(zhí)行成千上萬次。事實上,一個對象的泄漏只有很少的字節(jié)。在運行示例并將瀏覽器導航到一個空白頁面,你將會看到兩個版本代碼在內(nèi)存使用上的區(qū)別。當我們使用第一種方法,將子元素加入其父元素再將構(gòu)成的子樹加入頁面DOM,我們的內(nèi)存使用量會有微小的上升。這就是一個交叉導航泄漏,只有當我們重新啟動IE進程這些泄漏的內(nèi)存才會被釋放。如果你使用第二種方法將父元素加入頁面DOM再將子元素加入其父元

25、素中,同樣運行若干次后,你的內(nèi)存使用量將不會再上升,這時你會發(fā)現(xiàn)你已經(jīng)修復了交叉導航泄漏的問題。 function LeakMemory() var hostElement = document.getElementById(hostElement); / Do it a lot, look at Task Manager for memory response for(i = 0; i 5000; i+) var parentDiv = document.createElement(); var childDiv = document.createElement(); / This will

26、 leak a temporary object parentDiv.appendChild(childDiv); hostElement.appendChild(parentDiv); hostElement.removeChild(parentDiv); parentDiv.removeChild(childDiv); parentDiv = null; childDiv = null; hostElement = null; function CleanMemory() var hostElement = document.getElementById(hostElement); / D

27、o it a lot, look at Task Manager for memory response for(i = 0; i 5000; i+) var parentDiv = document.createElement(); var childDiv = document.createElement(); / Changing the order is important, this wont leak hostElement.appendChild(parentDiv); parentDiv.appendChild(childDiv); hostElement.removeChil

28、d(parentDiv); parentDiv.removeChild(childDiv); parentDiv = null; childDiv = null; hostElement = null; Memory Leaking Insert Clean Insert -這類泄漏應該被澄清,因為這個解決方法有悖于我們在IE中的一些有益經(jīng)驗。創(chuàng)建帶有腳本對象的DOM元素,以及它們已進行的相互關聯(lián)是了解這個泄漏的關鍵點。這實際上這對于泄漏來說是至關重要的,因為如果我們創(chuàng)建的DOM元素不包含任何的腳本對象,同時使用相同的方式將它們進行關聯(lián),我們是不會有任何泄漏問題的。示例中給出的第二種技巧對于關

29、聯(lián)大的子樹結(jié)構(gòu)可能更有效(由于在那個示例中我們一共只有兩個元素,所以建立一個和頁面DOM不相關的樹結(jié)構(gòu)并不會有什么效率問題)。第二個技巧是在創(chuàng)建元素的開始不關聯(lián)任何的腳本對象,所以你可以安全的創(chuàng)建子樹。當你把你的子樹關聯(lián)到頁面DOM上后,再繼續(xù)處理你需要的腳本事件。牢記并遵守關于循環(huán)引用和閉包函數(shù)的使用規(guī)則,你不會再在掛接事件時在你的代碼中遇到不同的泄漏。我真的要指出這個問題,因為我們可以看出不是所有的內(nèi)存泄漏都是可以很容易發(fā)現(xiàn)的。它們可能都是些微不足道的問題,但往往需要成千上萬次的執(zhí)行一個更小的泄漏場景才能使問題顯現(xiàn)出來,就像DOM元素插入順序引起的問題那樣。如果你覺得使用所謂的最佳經(jīng)驗來編

30、程,那么你就可以高枕無憂,但是這個示例讓我們看到,即使是最佳經(jīng)驗似乎也可能帶來泄漏。我們這里的解決方案希望能提高這些已有的好經(jīng)驗,或者介紹一些新經(jīng)驗使我們避免泄漏發(fā)生的可能。 貌似泄漏(Pseudo-Leaks) 在大多數(shù)時候,一些APIs的實際的行為和它們預期的行為可能會導致你錯誤的判斷內(nèi)存泄漏。貌似泄漏大多數(shù)時候總是出現(xiàn)在同一個頁面的動態(tài)腳本操作中,而在從一個頁面跳轉(zhuǎn)到空白頁面的時候發(fā)生是非常少見的。那你怎么能象排除頁面間泄漏那樣來排除這個問題,并且在新任務運行中的內(nèi)存使用量是否是你所期望的。我們將使用腳本文本的重寫來作為一個貌似泄漏的示例。象DOM插入順序問題那樣,這個問題也需要依賴創(chuàng)建臨時對象來產(chǎn)生泄漏。對一個腳本元素對象內(nèi)部的腳本

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論