电竞比分网-中国电竞赛事及体育赛事平台

分享

JavaScript 中的內(nèi)存泄露模式

 埃德溫會館 2011-10-26

JavaScript 中的內(nèi)存泄露模式

在 JavaScript 應(yīng)用程序中處理循環(huán)引用

Abhijeet Bhattacharya (abhbhatt@in.ibm.com), 系統(tǒng)軟件工程師, IBM India
Kiran Shivarama Shivarama Sundar, 系統(tǒng)軟件工程師, IBM India

簡介: 如果您知道內(nèi)存泄漏的起因,那么在 JavaScript 中進行相應(yīng)的防范就應(yīng)該相當(dāng)容易。在這篇文章中,作者 Kiran Sundar 和 Abhijeet Bhattacharya 將帶您親歷 JavaScript 中的循環(huán)引用的全部基本知識,向您介紹為何它們會在某些瀏覽器中產(chǎn)生問題,尤其是在結(jié)合了閉包的情況下。在了解了您應(yīng)該引起注意的常見內(nèi)存泄漏模式之后,您還將學(xué)到應(yīng)對這些泄漏的諸多方法。

發(fā)布日期: 2007 年 5 月 28 日
級別: 初級
訪問情況 : 6731 次瀏覽
評論: 1 (查看 | 添加評論 - 登錄)

平均分 3 星 共 22 個評分 平均分 (22個評分)
為本文評分

JavaScript 是用來向 Web 頁面添加動態(tài)內(nèi)容的一種功能強大的腳本語言。它尤其特別有助于一些日常任務(wù),比如驗證密碼和創(chuàng)建動態(tài)菜單組件。JavaScript 易學(xué)易用,但卻很容易在某些瀏覽器中引起內(nèi)存的泄漏。在這個介紹性的文章中,我們解釋了JavaScript 中的泄漏由何引起,展示了常見的內(nèi)存泄漏模式,并介紹了如何應(yīng)對它們。

注意本文假設(shè)您已經(jīng)非常熟悉使用 JavaScript 和 DOM 元素來開發(fā) Web 應(yīng)用程序。本文尤其適合使用 JavaScript 進行 Web 應(yīng)用程序開發(fā)的開發(fā)人員,也可供有興趣創(chuàng)建 Web 應(yīng)用程序的客戶提供瀏覽器支持以及負責(zé)瀏覽器故障排除的人員參考。

我的瀏覽器存在泄漏么?

Internet Explorer 和 Mozilla Firefox 是兩個與 JavaScript 中的內(nèi)存泄漏聯(lián)系最為緊密的瀏覽器。兩個瀏覽器中造成這種問題的“罪魁禍?zhǔn)住笔怯脕砉芾?DOM 對象的組件對象模型。本機Windows COM 和 Mozilla's XPCOM 都使用引用計數(shù)的垃圾收集來進行內(nèi)存分配和檢索。引用計數(shù)與用于 JavaScript 的標(biāo)記-清除式的垃圾收集并不總是能相互兼容。本文側(cè)重介紹的是如何應(yīng)對 JavaScript 代碼中的內(nèi)存泄漏。有關(guān)如何處理 Firefox 和 IE 中 COM 層內(nèi)存泄漏的更多信息,請參看 參考資料。

JavaScript 中的內(nèi)存泄漏

JavaScript 是一種垃圾收集式語言,這就是說,內(nèi)存是根據(jù)對象的創(chuàng)建分配給該對象的,并會在沒有對該對象的引用時由瀏覽器收回。JavaScript 的垃圾收集機制本身并沒有問題,但瀏覽器在為 DOM 對象分配和恢復(fù)內(nèi)存的方式上卻有些出入。

Internet Explorer 和 Mozilla Firefox 均使用引用計數(shù)來為 DOM 對象處理內(nèi)存。在引用計數(shù)系統(tǒng),每個所引用的對象都會保留一個計數(shù),以獲悉有多少對象正在引用它。如果計數(shù)為零,該對象就會被銷毀,其占用的內(nèi)存也會返回給堆。雖然這種解決方案總的來說還算有效,但在循環(huán)引用方面卻存在一些盲點。

循環(huán)引用的問題何在?

當(dāng)兩個對象互相引用時,就構(gòu)成了循環(huán)引用,其中每個對象的引用計數(shù)值都被賦 1。在純垃圾收集系統(tǒng)中,循環(huán)引用問題不大:若涉及到的兩個對象中的一個對象被任何其他對象引用,那么這兩個對象都將被垃圾收集。而在引用計數(shù)系統(tǒng),這兩個對象都不能被銷毀,原因是引用計數(shù)永遠不能為零。在同時使用了垃圾收集和引用計數(shù)的混合系統(tǒng)中,將會發(fā)生泄漏,因為系統(tǒng)不能正確識別循環(huán)引用。在這種情況下,DOM 對象和 JavaScript 對象均不能被銷毀。清單 1 顯示了在 JavaScript 對象和 DOM 對象間存在的一個循環(huán)引用。


清單 1. 循環(huán)引用導(dǎo)致了內(nèi)存泄漏
                

	<html>
     	<body>
     	<script type="text/javascript">
     	document.write("circular references between JavaScript and DOM!");
     	var obj;
     	window.onload = function(){
		obj=document.getElementById("DivElement");
            	document.getElementById("DivElement").expandoProperty=obj;
            	obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
            	};
     	</script>
     	<div id="DivElement">Div Element</div>
     	</body>
     	</html>


如上述清單中所示,JavaScript 對象 obj 擁有到 DOM 對象的引用,表示為DivElement。而 DOM 對象則有到此 JavaScript 對象的引用,由 expandoProperty 表示??梢?,JavaScript 對象和 DOM 對象間就產(chǎn)生了一個循環(huán)引用。由于 DOM 對象是通過引用計數(shù)管理的,所以兩個對象將都不能銷毀。

另一種內(nèi)存泄漏模式

在清單 2 中,通過調(diào)用外部函數(shù) myFunction 創(chuàng)建循環(huán)引用。同樣,JavaScript 對象和 DOM 對象間的循環(huán)引用也會導(dǎo)致內(nèi)存泄漏。


清單 2. 由外部函數(shù)調(diào)用引起的內(nèi)存泄漏
                

	<html>
	<head>
	<script type="text/javascript">
	document.write(" object s between JavaScript and DOM!");
	function myFunction(element)
	{
		this.elementReference = element;
		// This code forms a circular reference here
		//by DOM-->JS-->DOM
		element.expandoProperty = this;
	}
	function Leak() {
		//This code will leak
		new myFunction(document.getElementById("myDiv"));
	}
	</script>
	</head>
	<body onload="Leak()">
	<div id="myDiv"></div>
	</body>
	</html>


正如這兩個代碼示例所示,循環(huán)引用很容易創(chuàng)建。在 JavaScript 最為方便的編程結(jié)構(gòu)之一:閉包中,循環(huán)引用尤其突出。


JavaScript 中的閉包

JavaScript 的過人之處在于它允許函數(shù)嵌套。一個嵌套的內(nèi)部函數(shù)可以繼承外部函數(shù)的參數(shù)和變量,并由該外部函數(shù)私有。清單 3 顯示了內(nèi)部函數(shù)的一個示例。


清單 3. 一個內(nèi)部函數(shù)
                

	function parentFunction(paramA)
	{
    		var a = paramA;
    		function childFunction()
    		{
			return a + 2;
    		}
    		return childFunction();
	}


JavaScript 開發(fā)人員使用內(nèi)部函數(shù)來在其他函數(shù)中集成小型的實用函數(shù)。如清單 3 所示,此內(nèi)部函數(shù) childFunction 可以訪問外部函數(shù) parentFunction 的變量。當(dāng)內(nèi)部函數(shù)獲得和使用其外部函數(shù)的變量時,就稱其為一個閉包

了解閉包

考慮如清單 4 所示的代碼片段。


清單 4. 一個簡單的閉包
                

	<html>
	<body>
	<script type="text/javascript">
	document.write("Closure Demo!!");
	window.onload=
	function  closureDemoParentFunction(paramA)
	{
   		var a = paramA;
   		return function closureDemoInnerFunction (paramB)
   		{
     			alert( a +" "+ paramB);
   		};
	};
	var x = closureDemoParentFunction("outer x");
	x("inner x");
	</script>
	</body>
	</html>


在上述清單中,closureDemoInnerFunction是在父函數(shù) closureDemoParentFunction 中定義的內(nèi)部函數(shù)。當(dāng)用外部的 xclosureDemoParentFunction 進行調(diào)用時,外部函數(shù)變量 a 就會被賦值為外部的 x。函數(shù)會返回指向內(nèi)部函數(shù) closureDemoInnerFunction 的指針,該指針包括在變量 x 內(nèi)。

外部函數(shù) closureDemoParentFunction 的本地變量 a 即使在外部函數(shù)返回時仍會存在。這一點不同于 C/C++ 這樣的編程語言,在 C/C++ 中,一旦函數(shù)返回,本地變量也將不復(fù)存在。在 JavaScript 中,在調(diào)用 closureDemoParentFunction 的時候,帶有屬性 a 的范圍對象將會被創(chuàng)建。該屬性包括值 paramA,又稱為“外部 x”。同樣地,當(dāng) closureDemoParentFunction 返回時,它將會返回內(nèi)部函數(shù) closureDemoInnerFunction,該函數(shù)包括在變量 x 中。

由于內(nèi)部函數(shù)持有到外部函數(shù)的變量的引用,所以這個帶屬性 a 的范圍對象將不會被垃圾收集。當(dāng)對具有參數(shù)值 inner xx 進行調(diào)用時,即 x("inner x"),將會彈出警告消息,表明 “outer x innerx”。

清單 4 簡要解釋了 JavaScript 閉包。閉包功能非常強大,原因是它們使內(nèi)部函數(shù)在外部函數(shù)返回時也仍然可以保留對此外部函數(shù)的變量的訪問。不幸的是,閉包非常易于隱藏 JavaScript 對象 和 DOM 對象間的循環(huán)引用。


閉包和循環(huán)引用

在清單 5 中,可以看到一個閉包,在此閉包內(nèi),JavaScript 對象(obj)包含到 DOM 對象的引用(通過 id "element" 被引用)。而 DOM 元素則擁有到 JavaScript obj 的引用。這樣建立起來的 JavaScript 對象和 DOM 對象間的循環(huán)引用將會導(dǎo)致內(nèi)存泄漏。


清單 5. 由事件處理引起的內(nèi)存泄漏模式
                

	<html>
	<body>
	<script type="text/javascript">
	document.write("Program to illustrate memory leak via closure");
	window.onload=function outerFunction(){
		var obj = document.getElementById("element");
		obj.onclick=function innerFunction(){
		alert("Hi! I will leak");
		};
		obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
		// This is used to make the leak significant
	};
	</script>
	<button id="element">Click Me</button>
	</body>
	</html>



避免內(nèi)存泄漏

幸好,JavaScript 中的內(nèi)存泄漏是可以避免的。當(dāng)確定了可導(dǎo)致循環(huán)引用的模式之后,正如我們在上述章節(jié)中所做的那樣,您就可以開始著手應(yīng)對這些模式了。這里,我們將以上述的 由事件處理引起的內(nèi)存泄漏模式 為例來展示三種應(yīng)對已知內(nèi)存泄漏的方式。

一種應(yīng)對 清單5 中的內(nèi)存泄漏的解決方案是讓此 JavaScript 對象 obj 為空,這會顯式地打破此循環(huán)引用,如清單 6 所示。


清單 6. 打破循環(huán)引用
                

	<html>
	<body>
	<script type="text/javascript">
	document.write("Avoiding memory leak via closure by breaking the circular
    reference");
		window.onload=function outerFunction(){
		var obj = document.getElementById("element");
		obj.onclick=function innerFunction()
		{
			alert("Hi! I have avoided the leak");
			// Some logic here
		};
		obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
		obj = null; //This breaks the circular reference
		};
	</script>
	<button id="element">"Click Here"</button>
	</body>
	</html>


清單 7 是通過添加另一個閉包來避免 JavaScript 對象和 DOM 對象間的循環(huán)引用。


清單 7. 添加另一個閉包
                

	<html>
	<body>
	<script type="text/javascript">
	document.write("Avoiding a memory leak by adding another closure");
 	window.onload=function outerFunction(){
	var anotherObj = function innerFunction()
			 {
				// Some logic here
				alert("Hi! I have avoided the leak");
		  	 };
		 (function anotherInnerFunction(){
			var obj =  document.getElementById("element");
			obj.onclick=anotherObj })();
		    };
	</script>
	<button id="element">"Click Here"</button>
	</body>
	</html>


清單 8 則通過添加另一個函數(shù)來避免閉包本身,進而阻止了泄漏。


清單 8. 避免閉包自身
                

	<html>
	<head>
	<script type="text/javascript">
	document.write("Avoid leaks by avoiding closures!");
	window.onload=function()
	{
		var obj = document.getElementById("element");
		obj.onclick = doesNotLeak;
	}
	function doesNotLeak()
	{
		//Your Logic here
		alert("Hi! I have avoided the leak");
	}

	</script>
	</head>
	<body>
	<button id="element">"Click Here"</button>
	</body>
	</html>



結(jié)束語

本文解釋了循環(huán)引用是如何導(dǎo)致 JavaScript 中的內(nèi)存泄漏的 —— 尤其是在結(jié)合了閉包的情況下。您還了解了涉及到循環(huán)引用的一些常見內(nèi)存泄漏模式以及應(yīng)對這些泄漏模式的幾種簡單方式。有關(guān)本文所討論的主題的更多信息,請參看 參考資料


參考資料

學(xué)習(xí)

獲得產(chǎn)品和技術(shù)

  • IBM 產(chǎn)品的評估版:實際體驗來自 DB2?、Lotus?、Rational?、Tivoli? 和 WebSphere? 的應(yīng)用程序開發(fā)工具和中間件產(chǎn)品。

討論

作者簡介

Abhijeet Bhattacharya photo

Abhijeet Bhattacharya 是 IBM 印度軟件實驗室的一名系統(tǒng)工程師。在過去三年中,他一直是 OS/2 IBM Web Browser 支持團隊中的一員。他也具有系統(tǒng)管理領(lǐng)域的相關(guān)經(jīng)驗,并參與過 IBM Pegasus 開源創(chuàng)新項目。他目前工作的重點包括分布式計算和 SARPC。他擁有 Rajiv Gandhi Technical University 的工程學(xué)士學(xué)位。

Kiran Shivarama Sundar photo

Kiran Shivarama Sundar 是 IBM 印度軟件實驗室的一名系統(tǒng)工程師。在過去三年中,他一直是 OS/2 IBM Web Browser 支持團隊中的一員。他同時也具有諸多其他項目的工作經(jīng)驗,包括為 Apache Tuscany Open Source Project 開發(fā)命令行工具以及為 IBM 的 EPCIS 團隊開發(fā) RFIDIC Installer。目前,Kiran 加入了 IBM WebSphere Adapters 支持團隊,負責(zé)提供對 JMS 和 MQ 適配器的支持。他已成功獲得了 Sun Certified Java Programmer、Sun Certified Web Component Developer 和 Sun Certified Business Component Developer 的認(rèn)證。他目前所關(guān)注的領(lǐng)域包括 Java、J2EE、Web 服務(wù)和 SOA。他擁有 Visweshwaraya Technology University 的工程學(xué)士學(xué)位。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多