JavaScript Garbage

2025 年 3 月 18 日 | 阅读 7 分钟

JavaScript 为内存管理提供了高效的垃圾回收机制。JavaScript 自动支持内存管理,并且对我们来说是不可见的。当我们创建新的对象、函数、原始值和变量时,所有这些编程元素都会占用内存。JavaScript 如何管理这些元素并清理它们?

在本节中,我们将讨论 JavaScript 中内存管理的所有内部流程。我们将重点关注以下主题:

  • JavaScript 中的内存生命周期
  • JavaScript 中的内存分配
  • 内存不再使用时进行释放
  • JavaScript 垃圾回收
  • JavaScript 垃圾回收中的引用概念
  • 标记-清除算法

C 等其他编程语言支持手动内存管理方法,如 malloc() 和 free()。相比之下,JavaScript 会自动为新对象分配内存,并在不再使用时释放它们。因此,JavaScript 是 Web 上最快的编程语言之一。

让我们通过理解 JavaScript 中的内存管理来进一步探讨 JavaScript 垃圾回收。

内存生命周期

几乎所有编程语言的内存生命周期都非常相似。它按以下方式工作:

  1. 为新实例分配所需的内存
  2. 当实例被调用时,使用分配的内存(读取、写入)
  3. 最后,当不再使用时释放分配的内存。

但是,第二部分在所有编程语言中可能因其架构和用法而异。第一步和第三步在 C 等低级语言中是显式的,但在 JavaScript 等高级语言中,它们大多是隐式的。

JavaScript 中的内存分配

值初始化

在 JavaScript 中声明值时,它们会自动分配内存。我们不需要手动为创建的变量和对象分配内存。

让我们看下面的例子,它解释了不同类型变量和对象的内存分配。

从上面的例子中,我们可以看到 JavaScript 为每个对象、变量和方法分配了值。

函数调用

在 JavaScript 中,某些函数调用可能导致对象分配。例如,如果我们通过调用 Date() 函数创建一个日期对象,那么它也会为 Date 对象分配内存。请看下面的示例。

某些方法也会占用 JavaScript 内存空间。

读取和写入值

在 JavaScript 中使用值时,它们会与分配的内存进行交互,以读取和写入它们。我们可以从变量或对象属性的分配内存中读取和写入值,甚至可以将参数传递给函数。

内存不再使用时进行释放

当需要释放分配的内存时,大多数内存管理问题都会在这个阶段发生。在此阶段,最复杂的任务是确定何时不再需要特定的变量、对象或函数。

低级编程语言要求开发人员手动指定何时要释放已分配实例的内存。

但是,JavaScript 等高级语言使用自动内存管理,称为垃圾回收。

垃圾回收的主要用途是自动监控并找出何时特定块不再使用,然后回收它。

然而,自动内存管理过程完全基于近似。因此,确定某个特定内存块是否仍需要是不可判定的,这是一个普遍存在的问题。这时 JavaScript 垃圾回收器就派上用场了,并有效地管理内存。

现在我们已经讨论了 JavaScript 中的内存管理。让我们继续我们的话题:垃圾回收。

JavaScript 垃圾回收

如前所述,垃圾回收器面临的主要问题是自动确定特定内存是否会被利用。在这种情况下,垃圾回收器会为该问题的解决方案设置一个限制。为了解决这个问题,JavaScript 使用了引用的概念。让我们理解引用的概念。

JavaScript 垃圾回收中的引用概念

为了实现自动内存管理,JavaScript 依赖于引用的概念。在此概念中,在内存管理上下文中,如果一个对象稍后在应用程序中被访问(无论是隐式还是显式访问),那么它就被认为是另一个对象的引用。例如,一个对象会有一个指向其原型的隐式引用,以及指向其属性值的显式引用。

在这种情况下,对象的概念将扩展到比 JavaScript 中的常规对象更广泛的范围。它还包括对象的作用域。

引用计数垃圾回收

JavaScript 垃圾回收器遵循一种算法来计数引用。该算法通过确定一个对象是否仍有其他对象引用它来确定一个对象是否需要。如果一个对象没有指向它的引用,它将被确定为垃圾。

让我们通过下面的例子来理解它。

局限性(循环引用)

从上面的例子中,我们创建了两个对象,它们相互引用,形成了一个引用循环;这种情况称为循环引用。当 myFunction() 函数完成其执行时,它将超出作用域。此时它们将不再需要,但它们分配的内存仍将被占用。因此,垃圾回收器不会清理它们的分配。

然而,垃圾回收器的引用计数算法不会将它们识别为可回收对象,但它们各自都有一个引用;这就是为什么它们都不会被标记为垃圾回收。循环引用问题是应用程序中内存泄漏的原因之一。

IE 6 和 IE 7 维护引用计数垃圾回收器,但没有其他现代引擎使用引用计数垃圾回收器来防止因循环引用引起的内存泄漏。

标记-清除算法

标记-清除算法减少了“对象不再使用”算法的使用;相反,它侧重于“对象不可达”的方法。

这种方法侧重于根(一组对象)。根是 JavaScript 中的全局对象。在这种方法中,垃圾回收器从根(全局对象)开始,并查找从这些根指向的对象。它引用了来自这些根的所有对象。因此,它找到了所有可达和不可达的对象。

该算法是引用计数算法的改进。它会将零引用的对象视为不可达。因此,在该算法中,即使对象存在循环引用,它也不会被视为不可达。

截至 2012 年,所有现代浏览器都遵循标记-清除算法进行垃圾回收。过去几年 JavaScript 的所有改进都采用了这种方法,而不是其“对象不再需要”的原始方法。

在第一个例子中,函数调用后没有任何两个对象引用任何资源。因此,它们将被垃圾回收器视为不可达,并且它们的内存可以被回收。

局限性

在应用程序中,可能会有时方便手动指定要释放哪些内存。要释放对象的内存,我们需要手动使其显式不可达。

我们无法手动触发 JavaScript 垃圾回收器。

总结

JavaScript 为内存管理提供了高效的垃圾回收机制。JavaScript 自动支持内存管理,并且对我们来说是不可见的。我们无法手动触发 JavaScript 垃圾回收器。