一、記憶體泄漏 由於某些原因不再需要的記憶體沒有被操作系統或則空閑記憶體池回收。編程語言中有多種管理記憶體的方式。這些方式從不同程度上會減少記憶體泄漏的幾率,高級語言嵌入了一個名為垃圾收集器的軟體,其工作是跟蹤記憶體分配和使用,以便找到不再需要分配記憶體的時間,在這種情況下,它將自動釋放它。然而,該過程是近似的, ...
一、記憶體泄漏
由於某些原因不再需要的記憶體沒有被操作系統或則空閑記憶體池回收。編程語言中有多種管理記憶體的方式。這些方式從不同程度上會減少記憶體泄漏的幾率,高級語言嵌入了一個名為垃圾收集器的軟體,其工作是跟蹤記憶體分配和使用,以便找到不再需要分配記憶體的時間,在這種情況下,它將自動釋放它。然而,該過程是近似的,因為知道是否需要某些存儲器的一般問題是不可判定的(不能通過演算法來解決)。
1. 迴圈引用導致的記憶體泄漏
當兩個對象相互引用時,會形成一個迴圈引用,使每個對象的引用計數為1,在純粹的垃圾收集系統中,迴圈引用不是問題:如果任何其他對象都不引用所涉及的對象,則兩者都是會被視為垃圾而回收。但是,在引用計數系統中,兩個對象都不能被銷毀,因為引用計數永遠不會減到零。在使用垃圾回收和引用計數的混合系統中,由於系統無法識別迴圈引用而導致泄漏。在這種情況下,DOM對象和Javascript對象都不會被破壞。
<html> <body> <script type = "text/javascript"> document.write("Circular referances between Javascript and DOM!"); var obj; window.onload = function() { obj = document.getElementById("DivElement"); document.getElementById("DivElement").expandoProperty = obj; Array(1000).join(new Array(2000).join("XXXXX")); } </script> <div id="DivElement">Div Element</div> </body> </html>
如上面代碼所示,Javascript對象obj引用了DivElement表示的DOM對象。DOM對象反過來又通過expandoProperty對Javascript對象有一個引用。Javascript對象和DOM對象之間存在迴圈引用。因為DOM對象通過引用計數進行管理,所以兩個對象都不會被銷毀。
2. 外部函數引起的迴圈引用
下麵代碼中,通過調用外部函數myFunction來創建迴圈引用。Javascript對象和DOM對象之間的迴圈引用將最終導致記憶體泄漏。
<html> <head> <script type= "text/javascript"> document.write("Circular references between Javascript and DOM!"); function myFunction(element) { this.elementReferences = element; //this code forms a circular references 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>
正如上面這兩類代碼示例所顯示的,迴圈很容易創建。他們還傾向於在Javascript中最方便的編程結構:閉包。
3. 閉包引起的記憶體泄漏
Javascript的優點之一是它允許函數嵌套在其他函數之中,嵌套內部函數可以繼承外部函數的參數和變數,並且對該函數是私有的。Javascript開發人員使用內部函數將小效用函數集成到其他函數中,使得內部函數(childFunction)可以訪問外部parentFunction的變數。當一個內部函數獲取並使用對其外部函數變數的訪問時,它稱為閉包。
一個簡單的閉包例子
<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是父函數closureDemoParentFunction中定義的內部函數。當用外部x的參數對closureDemoParentFunction進行調用時,外部函數變數a被賦值外部x。函數返回一個指向內部函數closureDemoInnerFunction的指針,它包含在變數x中。必須註意的是,外部函數closureDemoParentFunction的局部變數a即使在外部函數返回後也會存在。這與C++等編程語言不同,在函數返回後,局部變數不再存在。在Javascript中,調用closureDemoParentFunction的時刻,創建一個具有屬性a的作用域對象。此屬性包含paramA的值,也稱為"outer x"。同樣,當closureDemoParentFunction 返回時,它將返回內部函數closureDemoInnerFunction,它包含在變數x中。
由於內部函數持有對外部函數的變數的引用,因此具有屬性a的作用域對象不會被垃圾回收。當在x上用一個參數值(即x("inner x")進行調用時,將彈出一個顯示"outer x inner x"的警報。閉包功能強大,因為它們允許內部函數在外部函數返回後保留對外部函數變數的訪問權想。遺憾的是,閉包在Javascript對象和DOM對象之間隱藏迴圈引用非常出色。
由於IE9之前的版本對Javascript對象和COM對象使用不同的垃圾回收常式,因此閉包在這些版本中會導致一些特殊的問題。具體來說,如果閉包的作用域中保存著一個HTML元素,那麼就意味著該元素將無法被銷毀。
function assignHandler() { var element = document.getElementById("my_btn"); element.onclick = function() { alert(element.id); }; }
以上代碼創建了一個作為element元素事件處理程式的閉包,而這個閉包又創建了一個迴圈引用。由於匿名函數保存了一個對assignHandler()的活動對象的引用,因此就會導致無法減少element的引用數。只要匿名函數存在,element的引用數至少也是1,因此它占用的記憶體永遠也會被回收。不過,這個問題是可以被解決的:
function assignHandler() { var element = document.getElementById("my_btn"); var id = ele.id; element.onclick = function() { alert(id); }; element = null; }
上面代碼,是把element.id的一個副本保存在一個變數中,並且在閉包中引用該變數消除迴圈引用,但是,這種程度還不能解決記憶體泄露的問題。必須要記住:閉包會引用包含函數的整個活動對象,而這其中包含著element。即使閉包不直接引用element,包含函數的活動對象中也仍然會保存著一個引用。因此,必須要把element變數設置為null。這樣就能解除對DOM對象的引用,順利減少引用次數,確保回收其占用的記憶體。
4. 事件處理程式引起的記憶體泄漏
在下麵的代碼中,你將會發現,一個JavaScript對象(obj)包含對DOM對象(由id"元素"引用)的引用的閉包。DOM元素反過來又具有對Javascript obj的引用。在Javascript對象和DOM對象之間產生的迴圈引用會導致記憶體泄漏。
<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")); }; </script> <button id="element">Click Me</button> </body> </html>
5. 避免記憶體泄漏
在Javascript中,記憶體泄露的另一方面是你可以避免它們。當您確定了可以導致迴圈引用的模式時,正如前面所列舉的那樣,您可以開始圍繞它們進行工作。我們將使用上面三種的事件處理中記憶體泄漏的方式解決已知記憶體泄露的方法。一個簡單的堅決方案是使Javascript對象obj設為null,從而顯式中斷迴圈引用。
<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"); // 一些邏輯代碼 }; obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX")); obj = null; //顯示中斷迴圈引用 }; </script> <button id="element">"Click Here"</button> </body> </html>
另一種方法是通過添加一個閉包,可以避免Javascript對象和DOM對象之間的迴圈引用。
<html> <body> <script type="text/javascript"> document.write("Avoiding memory leak via closure by adding another closure"); window.onload=function outerFunction(){ var anotherObj=function innerFunction() { 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>
第三種方法可以通過添加里那個一個函數來避免閉包,從而防止泄露。
<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>
6. 在Chrome中查找記憶體泄漏
Chrome提供了一系列優秀的工具來分析JavaScript代碼的記憶體使用。涉及與記憶體相關的兩幅圖:timeline視圖和profile視圖。
參考:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#Release_when_the_memory_is_not_needed_anymore