Javascript 內存管理

簡介

低級語言,好比C,有低級的內存管理基元,像malloc(),free()。另外一方面,JavaScript的內存基元在變量(對象,字符串等等)建立時分配,而後在他們再也不被使用時「自動」釋放。後者被稱爲垃圾回收。這個「自動」是混淆並給JavaScript(和其餘高級語言)開發者一個錯覺:他們能夠不用考慮內存管理。程序員

內存生命週期

無論什麼程序語言,內存生命週期基本一致:   web

  1. 分配你所須要的內存算法

  2. 使用它(讀、寫)數組

  3. 當它不被使用時釋放   ps:和「把大象裝冰箱「一個意思瀏覽器

第一二部分過程在全部語言中都很清晰。最後一步在低級語言中很清晰,可是在像JavaScript等高級語言中,最後一步不清晰。ide

JavaScript的內存分配

變量初始化

爲了避免讓程序員爲分配費心,JavaScript在定義變量時完成內存分配。函數

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

經過函數調用的內存分配

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

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

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

var s = "azerty";
var s2 = s.substr(0, 3); // s2 is a new string//由於string是不變量,JavaScript可能沒有分配內存,但只是存儲了0-3的範圍。
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2); // 新數組中有鏈接數組a和數組a2中的四個元素。

值的使用

使用值的過程其實是對分配內存進行讀取與寫入的操做,這意味着能夠寫入一個變量或者一個對象的屬性值,甚至傳遞函數的參數。prototype

當內存再也不須要使用時釋放

大多數內存管理的問題都在這個階段。在這裏最艱難的任務是找到「所分配的內存確實已經再也不須要了」。它每每要求開發人員來肯定在程序中哪一塊內存再也不須要而且釋放它。

高級語言解釋器嵌入了「垃圾回收器」,主要工做是跟蹤內存的分配和使用,以便當分配的內存再也不使用時,自動釋放它。這個過程是一個近似的,由於要知道某塊內存是否須要是 沒法斷定的 (沒法被某種算法所解決).

垃圾回收

如上文所述自動尋找是否一些內存「再也不須要」的問題是沒法斷定的。所以,垃圾回收實現只能有限制的解決通常問題。本節將解釋必要的概念,瞭解主要的垃圾回收算法和它們的侷限性。

引用

垃圾回收算法主要依賴於引用的概念。在內存管理的環境中,一個對象若是有訪問另外一個對象的權限(隱式或者顯式),叫作一個對象引用另外一個對象。例如,一個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 = document.createElement("div");div.onclick = function(){
  doSomething();}; // div有了一個引用指向事件處理屬性onclick// 事件處理也有一個對div的引用能夠在函數做用域中被訪問到// 這個循環引用會致使兩個對象都不會被垃圾回收

標記-清除算法

這個算法把「對象是否再也不須要」簡化定義爲「對象是否能夠得到」。

這個算法假定設置一個叫作的對象(在Javascript裏,根是全局對象)。按期的,垃圾回收器將從根開始,找全部從根開始引用的對象,而後找這些對象引用的對象……從根開始,垃圾回收器將找到全部能夠得到的對象和全部不能得到的對象。

這個算法比前一個要好,由於「有零引用的對象」老是不可得到的,可是相反卻不必定,參考「循環引用」。

從2012年起,全部現代瀏覽器都使用了標記-清除垃圾回收算法。全部對JavaScript垃圾回收算法的改進都是基於標記-清除算法的改進,並無改進標記-清除算法自己和它對「對象是否再也不須要」的簡化定義。

循環引用再也不是問題了

在上面的示例中,函數調用返回以後,兩個對象從全局對象出發沒法獲取。所以,他們將會被垃圾回收器回收。

第二個示例一樣,一旦 div 和其事件處理沒法從根獲取到,他們將會被垃圾回收器回收

限制: 對象須要明確的不可得到

儘管這是一個限制,可是不多會被突破,這也就是爲何在現實中不多人會去關心垃圾回收機制。

參考

相關文章
相關標籤/搜索