javascript内存管理

简介

JavaScript 在变量(对象,字符串等等)建立时分配内存,而后在它们再也不使用时“自动”释放。后者被称为垃圾回收。“自动”这个词容易让人混淆,或者说迷惑,并给JavaScript(和其余高级语言)开发者一个印象:他们能够不用考虑内存管理。然而这是错误的。javascript

内存生命周期

无论什么程序语言,内存生命周期基本是一致的:   java

  1. 分配你所须要的内存
  2. 使用分配到的内存(读、写)
  3. 不须要时将其释放\归还

在全部语言中第一和第二部分都很清晰。最后一步在低级语言中很清晰,可是在像JavaScript 等高级语言中,这一步是隐藏着的。程序员

JavaScript 的内存分配

值的初始化

为了避免让程序员费心分配内存,JavaScript 在定义变量时就完成了内存分配web

// 给数值变量分配内存
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);

经过函数调用的内存分配

有些函数调用结果是分配对象内存:算法

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 的结果

值的使用

使用值的过程其实是对分配内存进行读取与写入的操做。读取与写入多是写入一个变量或者一个对象的属性值,甚至传递函数的参数。浏览器

当内存再也不须要使用时释放

大多数内存管理的问题都在这个阶段。在这里最艰难的任务是找到“所分配的内存确实已经再也不须要了”。它每每要求开发人员来肯定在程序中哪一块内存再也不须要而且释放它。ide

高级语言解释器嵌入了“垃圾回收器”,它的主要工做是跟踪内存的分配和使用,以便当分配的内存再也不使用时,自动释放它。这只能是一个近似的过程,由于要知道是否仍然须要某块内存是没法断定的 (没法经过某种算法解决).函数

垃圾回收

如上文所述自动寻找是否一些内存“再也不须要”的问题是没法断定的。所以,垃圾回收实现只能有限制的解决通常问题。本节将解释必要的概念,了解主要的垃圾回收算法和它们的局限性。ui

引用

垃圾回收算法主要依赖于引用的概念。在内存管理的环境中,一个对象若是有访问另外一个对象的权限(隐式或者显式),叫作一个对象引用另外一个对象。例如,一个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 和其事件处理没法从根获取到,他们将会被垃圾回收器回收

 

限制: 那些没法从根对象查询到的对象都将被清除

尽管这是一个限制,但实践中咱们不多会碰到相似的状况,因此开发者不太会去关心垃圾回收机制。

参考

相关文章
相关标签/搜索