通过自动内存管理实现内存分配和闲置资源回收。基本思路:确定哪个变量不会再使用,然后释放它所占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间就会自动运行

标记未使用的变量有不同的实现方式,常见的主要有标记清理引用计数

# 1. 标记清理

js最常用的垃圾回收策略是标记清理。当变量进入上下文时,会被加上存在于上下文中的标记。当变量离开上下文时,也会被加上离开上下文的标记。标记过程不重要,关键是策略:垃圾回收程序运行的时候,最开始标记存储的所有变量。**然后,它会将上下文中的变量,以及被这些变量引用的变量的标记去掉。在此之后还剩下的标记的变量就是待删除的了。**因为任何在上下文中的变量都访问不到它们。虽然垃圾回收程序会做一次内存清理,销毁带标记的所有值并回收它们的内存。

# 2. 引用计数

另外一种没那么常用的垃圾回收策略是引用计数。声明并赋值时给变量加上引用数为1,如果该变量又被其他其他变量引用或赋值,则引用数再加1。同理,如果对该变量引用的变量被其他值赋值过后,则该引用数减1。当一个值的引用变量为0时,就说明上下文没有访问这个变量了,则垃圾回收程序会释放引用数为0的值的内存。

缺点:引用计数在循环引用时,会有严重问题。如果两个对象通过各自属性相互引用,那么它们的引用数永远不会变为0。如果函数被多次调用,则会导致大量的内存永远不会被释放。解决方法是将变量设置为null,会切断变量与其之前引用值的关系。下次垃圾回收程序运行时会被回收

# 性能

垃圾回收程序会周期性运行,如果内存中分配了很多变量,则可能造成性能损失,因此垃圾回收的时间调度很重要。

因此最好的办法是:无论什么时候开始收集垃圾,都能让它尽快结束工作。

在某些浏览器中是可以主动触发垃圾回收的,但不推荐。

# 内存管理

优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。如果数据不再必要,那么把它设置为null,从而释放引用。这也可以叫做解除引用。这个方法最适合全局变量全局对象的属性。因为局部变量在超出作用域后被自动解除引用。

注意:解除一个值的引用并不会自动导致相关内存被回收。而是为了确保该变量不在上下文中,下一次垃圾回收程序运行时回收它

  1. 通过 const 和 let 声明提升性能

在块作用域比函数作用域更早终止的情况下,const和let相比于使用var,使用这两个新关键字会更早地让垃圾回收程序介入,尽早回收应该回收的内存。

  1. 隐藏类和删除操作 V8 在将解释后的 JavaScript 代码编译为实际的机器码时会利用隐藏类。能够共享相同隐藏类的对象性能会更好
function Article() {
    this.title = 'Inauguration Ceremony Features Kazoo Band';
    this.age = 18
}
let a1 = new Article();
let a2 = new Article();

两个实例共享相同的隐藏类(v8引擎才能看见),因为这两个实例共享同一个构造函数和原型

如果加了下面两种代码

// 构造函数中没有author属性
// 动态添加
a2.author = 'Jake';

// 动态删除
delete a2.age

此时两个实例就会对应两个不同的隐藏类。会带来潜在的性能提升。最佳实践是把不想要的属性设置为null。这样就可以保持隐藏类不变和继续共享。同时也能达到删除引用值供垃圾回收程序回收的效果。

a1.author = null;

# 3. 内存泄漏

首先js的变量回收规则为:

  1. 全局变量不会被回收。
  2. 局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁。
  3. 只要被另外一个作用域所引用就不会被回收
  • 意外声明全局变量

    function setName() {
      name = 'lwl'
    }
    

    解释器会把变量name当作window的属性来创建(相当于window.name = 'lwl')。 可想而知,在window对象上创建的属性,只要window本身不被清理就不会消失。这个问题很容易解决,只要在变量声明前头加上varletconst关键字即可

  • 定时器

    let name = 'Jake';
    setInterval(() => {
        console.log(name);
    }, 100);
    

    定时器的回调通过闭包引用了外部变量,只要定时器一直运行,回调函数中引用的name就会一直占用内存。

  • 闭包

    let outer = function () {
        let name = 'Jake';
        return function () {
            return name;
        };
    };
    

    调用outer()会导致分配给name的内存被泄漏。以上代码执行后创建了一个内部闭包,只要返回的函数存在就不能清理name,因为闭包一直在引用着它。解决方法是:将不再使用的闭包实例手工删除。

原文: 浅谈JavaScript中的垃圾回收机制 (opens new window)