最新博客站點:歡迎來訪javascript
1、內存泄漏html
因爲某些緣由再也不須要的內存沒有被操做系統或則空閒內存池回收。編程語言中有多種管理內存的方式。這些方式從不一樣程度上會減小內存泄漏的概率,高級語言嵌入了一個名爲垃圾收集器的軟件,其工做是跟蹤內存分配和使用,以便找到再也不須要分配內存的時間,在這種狀況下,它將自動釋放它。然而,該過程是近似的,由於知道是否須要某些存儲器的通常問題是不可斷定的(不能經過算法來解決)。java
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對象之間的循環引用將最終致使內存泄漏。ide
<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 = element.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
更多內容請參考:
http://www.ruanyifeng.com/blog/2017/04/memory-leak.html