總所周知在JavaScript中也是有內存這個概念的。JavaScript
是在建立變量(對象,字符串等)時自動進行了分配內存,而且在不使用它們時「自動」釋放。 釋放的過程稱爲垃圾回收
。javascript
無論什麼程序語言,內存生命週期基本是一致的:java
全部語言第二部分都是明確的。第一和第三部分在底層語言中是明確的,但在像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 元素
複製代碼
有些方法分配新變量或者新對象:ui
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 的結果
複製代碼
使用值的過程其實是對分配內存進行
讀取
與寫入
的操做。讀取
與寫入
多是寫入一個變量或者一個對象的屬性值,甚至傳遞函數的參數。spa
大多數內存管理的問題都在這個階段。在這裏最艱難的任務是找到哪些被分配的內存確實已經再也不須要了
。它每每要求開發人員來肯定在程序中哪一塊內存再也不須要而且釋放它。code
高級語言解釋器嵌入了「垃圾回收器
」,它的主要工做是跟蹤內存的分配和使用,以便當分配的內存再也不使用時,自動釋放它。這隻能是一個近似的過程,由於要知道是否仍然須要某塊內存是沒法斷定的(沒法經過某種算法解決)。
如上文所述自動尋找是否一些內存再也不須要
的問題是沒法斷定的。所以,垃圾回收實現只能有限制的解決通常問題。
垃圾回收算法主要依賴於引用的概念。在內存管理的環境中,一個對象若是有訪問另外一個對象的權限(隱式或者顯式),叫作一個對象引用另外一個對象。例如,一個Javascript對象具備對它原型的引用(隱式引用)和對它屬性的引用(顯式引用)。
在這裏,「對象」的概念不只特指 JavaScript
對象,還包括函數做用域
(或者全局詞法做用域)。
這是最初級的垃圾收集算法。此算法把「對象是否再也不須要」簡化定義爲「對象有沒有其餘對象引用到它」。若是沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。
示例
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
屬性),而這個數據佔用的內存將永遠不會被釋放。
這個算法把「對象是否再也不須要
」簡化定義爲「對象是否能夠得到」。
這個算法假定設置一個叫作根(root)的對象(在Javascript
裏,根是全局對象)。垃圾回收器
將按期從根開始,找全部從根開始引用的對象,而後找這些對象引用的對象……從根開始,垃圾回收器
將找到全部能夠得到的對象和收集全部不能得到的對象。
這個算法比前一個要好,由於「有零引用的對象
」老是不可得到的,可是相反卻不必定,參考「循環引用」。
從2012年起,全部現代瀏覽器都使用了標記-清除垃圾回收算法
。全部對 JavaScript 垃圾回收算法
的改進都是基於標記-清除算法的改進,並無改進標記-清除算法自己和它對「對象是否再也不須要
」的簡化定義。
循環引用再也不是問題了
在上面的示例中,函數調用返回以後,兩個對象從全局對象出發沒法獲取。所以,他們將會被垃圾回收器回收。第二個示例一樣,一旦 div 和其事件處理沒法從根獲取到,他們將會被垃圾回收器回收。
限制: 那些沒法從根對象查詢到的對象都將被清除
儘管這是一個限制,但實踐中咱們不多會碰到相似的狀況,因此開發者不太會去關心垃圾回收機制。
看文章的帥哥靚女,既然翻到底部了,拿出手機關注我吧~