Skip to content
On this page

垃圾回收

难度:⭐️⭐️

💌 JS 是使用垃圾回收的语言,通过自动内存管理实现内存分配和闲置资源回收。

基本概念

  • 基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存
  • 这个过程是周期性
  • 垃圾回收过程是一个近似且不完美的方案,因为某块内存是否还有用,属于“不可判定的”问题,意味着靠算法是解决不了的

两种标记策略

垃圾回收程序必须跟踪记录哪个变量还会使用,以及哪个变量不会再使用,以便回收内存。如何标记未使用的变量也许有不同的实现方式。不过,在浏览器的发展史上,用到过两种主要的标记策略:标记清理引用计数

1.标记清理

标记清理(mark-and-sweep)是最常用的垃圾回收策略。

垃圾回收程序执行顺序:

  1. 标记内存中存储的所有变量(标记过程的实现并不重要,关键是策略)
  2. 所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉
  3. 在此之后,带有标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。

2.引用计数

另一种没那么常用的垃圾回收策略是引用计数(reference counting)。

引用计数在代码中存在循环引用时会出现问题,出现频率低,在此不做详细说明。

内存管理

分配给浏览器的内存通常比分配给桌面软件的要少很多,分配给移动浏览器的就更少。为了避免运行大量 JavaScript 的网页耗尽系统内存而导致操作系统崩溃,我们需要对内存管理有一定了解。

优化内存占用的最佳手段:保证在执行代码时只保存必要的数据。如果数据不再必要,那么把它设置为 null,从而释放其引用。这也可以叫作解除引用

js
function createPerson(name){
  let localPerson = new Object();
  localPerson.name = name;
  return localPerson;
}
let globalPerson = createPerson("Nicholas");
// 解除 globalPerson 对值的引用
globalPerson = null;

上面的最后一行代码就是这么做的。

不过要注意,解除对一个值的引用并不会自动导致相关内存被回收。解除引用的关键在于:确保相关的值已经不在上下文里了,因此它在下次垃圾回收时会被回收。

其他有助于内存管理的方法:

  • 通过 constlet 声明提升性能。因为 constlet 都以块(而非函数)为作用域,所以相比于使用 var,使用这两个新关键字可能会更早地让垃圾回收程序介入,尽早回收应该回收的内存。
  • 内存泄露
    • 意外声明全局变量是最常见但也最容易修复的内存泄漏问题:
    js
    function setName() {
      name = 'Jake';
    }
    
    解释器会把变量 name 当作 window 的属性来创建(相当于 window.name = 'Jake')。可想而知,在 window 对象上创建的属性,只要 window 本身不被清理就不会消失。
    • 使用 JavaScript 闭包很容易在不知不觉间造成内存泄漏
    js
    let outer = function() {
      let name = 'Jake';
      return function() {
        return name;
      };
    };
    
    以上代码执行后创建了一个内部闭包,只要返回的函数存在就不能清理 name,因为闭包一直在引用着它。