內存泄漏能夠定義爲程序再也不使用或不須要的一塊內存,可是因爲某種緣由沒有被釋放仍然被沒必要要的佔有。在代碼中建立對象和變量會佔用內存,可是javaScript是有本身的內存回收機制,能夠肯定那些變量再也不須要,並將其清除。可是當你的代碼存在邏輯缺陷的時候,你覺得你已經不須要,可是程序中還存在着引用,致使程序運行完後並無合適的回收所佔用的空間,致使內存不斷的佔用,運行的時間越長佔用的就越多,隨之出現的是,性能不佳,高延遲,頻繁崩潰。 在深刻了解內存泄漏以前,咱們須要知道一下幾點:javascript
無論什麼程序語言,內存生命週期基本是一致的:文章封面圖前端
全部語言第二部分都是明確的。第一和第三部分在底層語言中是明確的,但在像JavaScript這些高級語言中,大部分都是隱含的。java
不一樣的語言經過不一樣的方式來處理其內存。node
垃圾回收機制經過按期的檢查哪些先前分配的內存「仍然被須要」(×) 垃圾回收機制經過按期的檢查哪些先前分配的內存「程序的其餘部分仍然能夠訪問到的內存」(√)。算法
嗯? 一臉懵逼。。。。數組
這是理解垃圾回收的關鍵,只有開發者知道這一塊內存是否在將來使用「被須要」,可是能夠經過算法來肯定沒法訪問的內存並將其標記返回操做系統。瀏覽器
這是最初級的垃圾收集算法,若是沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。(MDN上面的例子)session
var o = {
a: {
b:2
}
};
// 兩個對象被建立,一個做爲另外一個的屬性被引用,
另外一個被分配給變量o
// 很顯然,沒有一個能夠被垃圾收集
var o2 = o; // o2變量是第二個對「這個對象」的引用
o = 1; // 如今,「這個對象」的原始引用o被o2替換了
var oa = o2.a; // 引用「這個對象」的a屬性
// 如今,「這個對象」有兩個引用了,一個是o2,一個是oa
o2 = "yo"; // 最初的對象如今已是零引用了
// 他能夠被垃圾回收了
// 然而它的屬性a的對象還在被oa引用,因此還不能回收
oa = null; // a屬性的那個對象如今也是零引用了
// 它能夠被垃圾回收了
複製代碼
缺陷:在循環的狀況下,引用計數算法存在很大的侷限性。閉包
function foo(){
var obj1 = {};
var obj2 = {};
obj1.x = obj2 ; // obj1引用obj2
obj2.x = obj1 ; // obj2引用obj1
return true ;
}
foo();
複製代碼
算法由如下幾步組成:併發
無圖言( )
循環引用的問題迎刃而解.雖然這個算法在不停的改進,js垃圾回收的(生成/增量/併發垃圾回收)這些改進的本質上仍是相同的: 可達內存被標記,其他的被看成垃圾回收。
缺點: 算法運行時程序執行被暫停。
雖然垃圾回收機制很好很方便,可是得本身權衡.緣由是咱們沒法肯定什麼時候會執行收集.只有開發人員才能明確是否能夠將一塊內存返回給操做系統。不須要的引用是指開發者明知內存引用再也不須要,而咱們在寫程序的時候卻因爲某些緣由,它仍被留在激活的 root 樹中。
垃圾收集語言泄漏的主要緣由是不須要的引用。要了解不須要的引用是什麼,首先咱們須要瞭解垃圾收集器如何肯定是否能夠訪問一塊內存。
所以,要了解哪些是JavaScript中最多見的泄漏,咱們須要知道引用一般被遺忘的方式。
在非嚴格模式下當引用未聲明的變量時,會在全局對象中建立一個新變量。在瀏覽器中,全局對象將是window,這意味着
function foo(arg){
bar =「some text」; // bar將泄漏到全局.
}
複製代碼
爲何不能泄漏到全局呢,咱們平時都會定義全局變量呢!!!
** 緣由 ** :全局變量是根據定義沒法被垃圾回收機制收集.須要特別注意用於臨時存儲和處理大量信息的全局變量。若是必須使用全局變量來存儲數據,請確保將其指定爲null或在完成後從新分配它。 ** 解決辦法 **: 嚴格模式
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
node.innerHTML = JSON.stringify(someResource));
// 定時器也沒有清除
}
// node、someResource 存儲了大量數據 沒法回收
}, 1000);
複製代碼
緣由:與節點或數據關聯的計時器再也不須要,node 對象能夠刪除,整個回調函數也不須要了。但是,計時器回調函數仍然沒被回收(計時器中止纔會被回收)。同時,someResource 若是存儲了大量的數據,也是沒法被回收的。
解決方法: 在定時器完成工做的時候,手動清除定時器
var refA = document.getElementById('refA');
document.body.removeChild(refA); // dom刪除了
console.log(refA, "refA"); // 可是還存在引用
能console出整個div 沒有被回收
複製代碼
緣由: 保留了DOM節點的引用,致使GC沒有回收
解決辦法:refA = null;
注意: 此外還要考慮 DOM 樹內部或子節點的引用問題。假如你的 JavaScript 代碼中保存了表格某一個 <td> 的引用。未來決定刪除整個表格的時候,直覺認爲 GC 會回收除了已保存的 <td> 之外的其它節點。實際狀況並不是如此:此 <td> 是表格的子節點,子元素與父元素是引用關係。因爲代碼保留了 <td> 的引用,致使整個表格仍待在內存中。保存 DOM 元素引用的時候,要當心謹慎。
注意
注意
注意: 閉包自己沒有錯,不會引發內存泄漏.而是使用錯誤致使.
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);
複製代碼
這是一段糟糕的代碼,每次調用 replaceThing ,theThing 獲得一個包含一個大數組和一個新閉包(someMethod)的新對象。同時,變量 unused 是一個引用 originalThing 的閉包(先前的 replaceThing 又調用了theThing)。思緒混亂了嗎?最重要的事情是,閉包的做用域一旦建立,它們有一樣的父級做用域,做用域是共享的。someMethod 能夠經過 theThing 使用,someMethod 與 unused 分享閉包做用域,儘管 unused 從未使用,它引用的 originalThing 迫使它保留在內存中(防止被回收)。當這段代碼反覆運行,就會看到內存佔用不斷上升,垃圾回收器(GC)並沒有法下降內存佔用。本質上,閉包的鏈表已經建立,每個閉包做用域攜帶一個指向大數組的間接的引用,形成嚴重的內存泄漏。
解決: 去除unuserd函數或者在replaceThing函數最後一行加上 originlThing = null.
參考:
4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them
How JavaScript works: memory management + how to handle 4 common memory leaks