JavaScript內存管理

NO IMAGE

總所周知在JavaScript中也是有內存這個概念的。JavaScript是在創建變量(對象,字符串等)時自動進行了分配內存,並且在不使用它們時“自動”釋放。 釋放的過程稱為垃圾回收

JavaScript內存管理

一、內存生命週期

不管什麼程序語言,內存生命週期基本是一致的:

  • 1、分配你所需要的內存
  • 2、使用分配到的內存(讀、寫)
  • 3、不需要時將其釋放\歸還

所有語言第二部分都是明確的。第一和第三部分在底層語言中是明確的,但在像JavaScript這些高級語言中,大部分都是隱含的。

1.1、JavaScript 的內存分配

值的初始化

為了不讓程序員費心分配內存,JavaScript 在定義變量時就完成了內存分配。

var a = 123; // 給數值變量分配內存
var b = "azerty"; // 給字符串分配內存
var c = {
a: 1,
b: null
}; // 給對象及其包含的值分配內存
// 給數組及其包含的值分配內存(就像對象一樣)
var d = [1, null, "abra"]; 
function f(a){
return a + 2;
} // 給函數(可調用的對象)分配內存
// 函數表達式也能分配一個對象
someElement.addEventListener('click', function(){
someElement.style.backgroundColor = 'blue';
}, false);

通過函數調用分配內存

有些函數調用結果是分配對象內存:

var d = new Date(); // 分配 Date 對象
var e = document.createElement('div'); // 分配 DOM 元素

有些方法分配新變量或者新對象:

var s = "azerty";
var s2 = s.substr(0, 3); // s2 是一個新的字符串
// 因為字符串是不變量,
// JavaScript 可能決定不分配內存,
// 只是存儲了 [0-3] 的範圍。
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2); 
// 新數組有四個元素,是 a 連接 a2 的結果

1.2、使用值

使用值的過程實際上是對分配內存進行讀取寫入的操作。讀取寫入可能是寫入一個變量或者一個對象的屬性值,甚至傳遞函數的參數。

1.3、當內存不再需要使用時釋放

大多數內存管理的問題都在這個階段。在這裡最艱難的任務是找到哪些被分配的內存確實已經不再需要了。它往往要求開發人員來確定在程序中哪一塊內存不再需要並且釋放它。

高級語言解釋器嵌入了“垃圾回收器”,它的主要工作是跟蹤內存的分配和使用,以便當分配的內存不再使用時,自動釋放它。這隻能是一個近似的過程,因為要知道是否仍然需要某塊內存是無法判定的(無法通過某種算法解決)。

JavaScript內存管理

二、垃圾回收

如上文所述自動尋找是否一些內存不再需要的問題是無法判定的。因此,垃圾回收實現只能有限制的解決一般問題。

2.1、引用

垃圾回收算法主要依賴於引用的概念。在內存管理的環境中,一個對象如果有訪問另一個對象的權限(隱式或者顯式),叫做一個對象引用另一個對象。例如,一個Javascript對象具有對它原型的引用(隱式引用)和對它屬性的引用(顯式引用)。

在這裡,“對象”的概念不僅特指 JavaScript 對象,還包括函數作用域(或者全局詞法作用域)。

2.2、引用計數垃圾收集

這是最初級的垃圾收集算法。此算法把“對象是否不再需要”簡化定義為“對象有沒有其他對象引用到它”。如果沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。

示例

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 f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return "azerty";
}
f();

實際例子

IE 6, 7 使用引用計數方式對 DOM 對象進行垃圾回收。該方式常常造成對象被循環引用時內存發生洩漏:

var div;
window.onload = function(){
div = document.getElementById("myDivElement");
div.circularReference = div;
div.lotsOfData = new Array(10000).join("*");
};

在如上的例子裡,myDivElement 這個 DOM 元素裡的 circularReference 屬性引用了 myDivElement,造成了循環引用。如果該屬性沒有顯示移除或者設為 null,引用計數式垃圾收集器將總是且至少有一個引用,並將一直保持在內存裡的 DOM 元素,即使其從 DOM 樹中刪去了。如果這個 DOM 元素擁有大量的數據 (如上的 lotsOfData屬性),而這個數據佔用的內存將永遠不會被釋放。

2.3、標記-清除算法

這個算法把“對象是否不再需要”簡化定義為“對象是否可以獲得”。

這個算法假定設置一個叫做根(root)的對象(在Javascript裡,根是全局對象)。垃圾回收器將定期從根開始,找所有從根開始引用的對象,然後找這些對象引用的對象……從根開始,垃圾回收器將找到所有可以獲得的對象和收集所有不能獲得的對象。

這個算法比前一個要好,因為“有零引用的對象”總是不可獲得的,但是相反卻不一定,參考“循環引用”。

從2012年起,所有現代瀏覽器都使用了標記-清除垃圾回收算法。所有對 JavaScript 垃圾回收算法的改進都是基於標記-清除算法的改進,並沒有改進標記-清除算法本身和它對“對象是否不再需要”的簡化定義。

循環引用不再是問題了

在上面的示例中,函數調用返回之後,兩個對象從全局對象出發無法獲取。因此,他們將會被垃圾回收器回收。第二個示例同樣,一旦 div 和其事件處理無法從根獲取到,他們將會被垃圾回收器回收。

限制: 那些無法從根對象查詢到的對象都將被清除

儘管這是一個限制,但實踐中我們很少會碰到類似的情況,所以開發者不太會去關心垃圾回收機制。

我的公眾號

看文章的帥哥靚女,既然翻到底部了,拿出手機關注我吧~

這是一個神奇的二維碼 ❤

JavaScript內存管理

相關文章

利用高效的css提高你的開發效率~(上)

CSS的簡寫屬性

HTTP狀態碼詳解(下)(建議收藏)

HTTP狀態碼詳解(上)(建議收藏)