在程序運行過程當中再也不用到的內存,沒有及時釋放,會出現內存泄漏(memory leak),會形成系統內存的浪費,致使程序運行速度減慢甚至系統崩潰等嚴重後果。javascript
而內存泄漏是每一個開發人員最終必須面對的問題。 即便使用內存管理語言,好比C語言有着malloc()
和 free()
這種低級內存管理語言也有可能出現泄露內存的狀況。html
這很麻煩,因此爲了減輕編程中的負擔,大多數語言提供了自動內存管理,這被稱爲"垃圾回收機制"(garbage collector)。java
如今各大瀏覽器一般採用的垃圾回收有兩種方法:標記清除(mark and sweep)、引用計數(reference counting)。node
一、標記清除git
這是javascript中最經常使用的垃圾回收方式。程序員
工做原理:當變量進入執行環境時,將這個變量標記爲「進入環境」。當變量離開環境時,則將其標記爲「離開環境」。標記「離開環境」的就回收內存。github
工做流程:算法
二、引用計數編程
工做原理:跟蹤記錄每一個值被引用的次數。數組
工做流程:
但若是一個值再也不須要了,引用數卻不爲0,垃圾回收機制沒法釋放這塊內存,會致使內存泄漏。
var arr = [1, 2, 3];
console.log('hello miqilin');
複製代碼
上面代碼中,數組[1, 2, 3]
會佔用內存,賦值給了變量arr
,所以引用次數爲1。儘管後面的一段代碼沒有用到arr
,它仍是會持續佔用內存。
若是增長一行代碼,解除arr對[1, 2, 3]
引用,這塊內存就能夠被垃圾回收機制釋放了。
var arr = [1, 2, 3];
console.log('hello miqilin');
arr = null;
複製代碼
上面代碼中,arr
重置爲null
,就解除了對[1, 2, 3]
的引用,引用次數變成了0,內存就能夠釋放出來了。
所以,並非說有了垃圾回收機制,程序員就無事一身輕了。你仍是須要關注內存佔用:那些很佔空間的值,一旦再也不用到,你必須檢查是否還存在對它們的引用。若是是的話,就必須手動解除引用。
接下來,我將介紹四種常見的JavaScript 內存泄漏及如何避免。目前水平有限,借鑑了國外大牛的文章瞭解這幾種內存泄漏,原文連接:blog.sessionstack.com/how-javascr…
1.意外的全局變量
未定義的變量會在全局對象建立一個新變量,對於在瀏覽器的狀況下,全局對象是window
。 看如下代碼:
function foo(arg) {
bar = "this is a hidden global variable";
}
複製代碼
函數foo
內部使用var
聲明,實際上JS會把bar
掛載在全局對象上,意外建立一個全局變量。等同於:
function foo(arg) {
window.bar = "this is an explicit global variable";
}
複製代碼
在上述狀況下, 泄漏一個簡單的字符串不會形成太大的傷害,但它確定會更糟。
另外一種能夠建立偶然全局變量的狀況是this
:
function foo() {
this.variable = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
複製代碼
解決方法:
在 JavaScript 文件頭部加上 'use strict'
,使用嚴格模式避免意外的全局變量,此時上例中的this
指向undefined
。若是必須使用全局變量存儲大量數據時,確保用完之後把它設置爲 null
或者從新定義。
2.被遺忘的計時器或回調函數
在JavaScript中使用setInterval
很是常見。
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// Do stuff with node and someResource.
node.innerHTML = JSON.stringify(someResource));
} }, 1000);
複製代碼
上面的代碼代表,在節點node
或者數據再也不須要時,定時器依舊指向這些數據。因此哪怕當node
節點被移除後,interval
仍舊存活而且垃圾回收器沒辦法回收,它的依賴也沒辦法被回收,除非終止定時器。
var element = document.getElementById('button');
function onClick(event) {
element.innerHtml = 'text';
}
element.addEventListener('click', onClick); // Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers that don't
// handle cycles well.
複製代碼
對於上面觀察者的例子,一旦它們再也不須要(或者關聯的對象變成不可達),明確地移除它們很是重要。其中IE 6 是沒法處理循環引用的。由於老版本的 IE 是沒法檢測 DOM 節點與 JavaScript 代碼之間的循環引用,會致使內存泄漏。
可是,現代的瀏覽器(包括 IE 和 Microsoft Edge)使用了更先進的垃圾回收算法(標記清除),已經能夠正確檢測和處理循環引用了。即回收節點內存時,沒必要非要調用removeEventListener
了。
諸如jQuery之類的框架和庫在處理節點以前會刪除偵聽器(當使用它們的特定API時)。 這由庫內部處理,並確保不會產生任何泄漏,即便在有問題的瀏覽器(如舊版Internet Explorer)下運行也是如此。
3.閉包
JavaScript 開發的一個關鍵知識是閉包:這是一個內部函數,它能夠訪問外部(封閉)函數的變量。因爲 JavaScript 運行時的實現細節,用下邊這種方式可能會形成內存泄漏:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: newArray(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);
複製代碼
每次調用replaceThing
,theThing
獲得一個包含一個大數組和一個新閉包(someMethod
)的新對象。同時,變量unused
是一個引用originalThing
的閉包(先前的replaceThing
又調用了theThing
)。someMethod
能夠經過theThing
使用,someMethod
與unused
分享閉包做用域,儘管unused
從未使用,它引用的originalThing
迫使它保留在內存中(防止被回收)。須要記住的是一旦一個閉包做用域被同一個父做用域的閉包所建立,那麼這個做用域是共享的。
全部這些均可能致使嚴重的內存泄漏。當上面的代碼片斷一次又一次地運行時,你能夠看到內存使用量的急劇增長。當垃圾收集器運行時,也不會減小。一個連接列表閉包被建立(在這種狀況下 theThing
變量是根源),每個閉包做用域對打數組進行間接引用。
解決方法:
在 replaceThing
的最後添加 originalThing = null
。將全部聯繫都切斷。
4.脫離 DOM 的引用
若是把DOM 存成字典(JSON 鍵值對)或者數組,此時,一樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另外一個在字典中。若是在未來某個時候您決定刪除這些行,則須要使兩個引用都沒法訪問,都清除掉。
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
// Much more logic
}
function removeButton() {
// The button is a direct child of body.
document.body.removeChild(document.getElementById('button'));
// At this point, we still have a reference to #button in the global
// elements dictionary. In other words, the button element is still in
// memory and cannot be collected by the GC.
}
複製代碼
若是代碼中保存了表格某一個<td>
的引用。未來決定刪除整個表格的時候,直覺認爲 GC 會回收除了已保存的<td>
之外的其它節點。實際狀況並不是如此:此<td>
是表格的子節點,子元素與父元素是引用關係。因爲代碼保留了<td>
的引用,致使整個表格仍待在內存中。因此保存 DOM 元素引用的時候,要當心謹慎。
在局部做用域中,等函數執行完畢,變量就沒有存在的必要了,js垃圾回收機制很快作出判斷而且回收,可是全局變量何時須要自動釋放內存空間則很難判斷,所以在咱們的開發中,須要儘可能避免使用全局變量。
咱們在使用閉包的時候,就會形成嚴重的內存泄漏,由於閉包的緣由,局部變量會一直保存在內存中,因此在使用閉包的時候,要多加當心。
若是有別的關於內存泄漏好的資源,能夠分享給我嘛謝謝了~
本人Github連接以下,歡迎各位Star