JavaScript 中的垃圾回收是什么以及它是如何工作的
垃圾回收并非新鲜事物。然而,许多 JavaScript 开发者对此知之甚少。如果您也是其中之一,也不用担心。本教程将帮助您了解 JavaScript 中垃圾回收的基础知识。您将了解垃圾回收的概念及其工作原理。
简要介绍
你可能已经听说过“垃圾回收”这个概念。如果没有,这里可以简单介绍一下。JavaScript 是一门独特的语言。与其他语言不同,JavaScript 能够在需要时自动分配内存。当不再需要内存时,它也可以释放内存。
创建新对象时,无需使用特殊方法为该对象分配内存。不再需要该对象时,也无需使用其他特殊方法释放内存。JavaScript 会为您完成这些操作。它会自动检查是否需要分配或释放内存。
如果有这样的需求,JavaScript 会完成必要的工作来满足该需求。它会在你毫不知情的情况下完成所有工作。这是一件好事,也是一件坏事。好事是因为你不必太担心。坏事是因为它会让你觉得你根本不用担心。
问题在于,JavaScript 只能在一定程度上帮助你进行内存管理。如果你开始设置障碍,它就帮不上忙了。在讨论垃圾回收之前,我们先快速了解一下内存和内存管理。
内存管理和内存生命周期
所有编程语言都具备一个共同点,那就是内存生命周期。这个生命周期描述了内存的管理方式。它由三个步骤组成。第一步是分配所需的内存。这发生在声明新变量并赋值、调用创建值的函数等过程中。
所有这些新值都需要一定的内存空间。JavaScript 会分配这些空间,并供您使用。第二步是使用分配的内存执行读写数据等任务。例如,当您想读取某个变量或对象属性的值,或者想更改该值或属性时。
第三步也是最后一步是释放分配的内存。当不再需要时,您需要释放分配的内存。例如,当您不再需要某个变量时,为什么要永久保留它呢?您希望 JavaScript 删除该变量,这样它就不会占用内存空间。
第三步至关重要。如果没有它,你的程序会持续消耗越来越多的内存,直到没有可用内存为止。然后,程序就会崩溃。这最后一步也是最难做到的。无论对于你这个低级语言开发者,还是对于语言本身而言,都是如此。
内存释放或车库收集
众所周知,JavaScript 会帮你处理内存管理。它会自动处理内存生命周期的所有三个步骤。这听起来不错,但垃圾回收机制呢?它在哪里发挥作用?答案很简单,在第三步。整个第三步,也就是释放分配的内存,都与垃圾回收机制有关。
垃圾收集及其工作原理
正如我们所讨论的,第三步是整个内存生命周期中最困难的一步。垃圾回收器如何知道哪些内存应该被释放呢?垃圾回收器使用了一些工具和技巧来解决这个问题。让我们逐一看看这些工具和技巧。
参考和可达性
垃圾回收机制所依赖的主要概念是引用和可达性。它区分可访问的值和不可访问的值。可访问的值是当前函数中的局部变量和参数。如果链中存在嵌套函数,则可访问的值也是这些嵌套函数的参数和变量。
最后,可到达的值也包括所有全局变量,即在全局作用域中定义的变量。所有这些可到达的值被称为“根”。然而,这不一定是终点。如果还有其他值,即可以通过引用或引用链从根到达的值,那么这些值也变为可到达的。
JavaScript 有一个称为垃圾收集器的特殊进程。此进程在后台运行。它的作用是监视所有现有对象。当某个对象变得无法访问时,垃圾收集器就会将其移除。我们来看一个简单的代码示例。
首先,我们声明一个新的全局变量,名为“toRead”,并赋值给它一个对象作为值。这个值将是 root,因为它位于全局作用域内,而变量“toRead”则充当了它所持有对象的引用,也就是该变量的值。
只要这个引用存在,它所持有的对象(变量值)就不会被垃圾收集器删除。它会一直留在内存中,因为它仍然是可访问的。
// Create object in a global scope, a root value
let toRead = { bookName: 'The Art of Computer Programming' }
// JavaScript allocates memory for object { bookName: 'The Art of Computer Programming' },
// the "toRead" becomes reference for this object
// this existing reference prevents { bookName: 'The Art of Computer Programming' } object
// from being removed by garbage collector
假设你不再需要该对象。一个简单的方法就是移除所有对它的引用,告诉 JavaScript 它已经冗余。此时,只有一个引用,即“toRead”变量。如果你移除这个引用,垃圾回收器会检测到它所引用的对象不再需要,并将其移除。
// Remove reference to { bookName: 'The Art of Computer Programming' } object
let toRead = null
// Garbage collector can now detect
// that the { bookName: 'The Art of Computer Programming' } object
// is no longer needed, no longer reachable, and it can remove it,
// release it from the memory
多个引用
另一种情况是,你有一个对象,并且有多个引用指向该对象。例如,你声明一个新的全局变量并赋值给它一个对象。之后,你声明另一个变量,并通过引用第一个变量将第一个对象赋值给它。
只要至少有一个引用存在,该对象就不会被移除。它所占用的内存空间也不会被释放。要实现这一点,您必须移除两个或更多现有引用。
// Create object in a global scope, a root value
let toRead = { bookName: 'The Art of Computer Programming' }
// This is the first reference to { bookName: 'The Art of Computer Programming' } object
// Create another reference for { bookName: 'The Art of Computer Programming' } object
let alreadyRead = toRead
这样做的结果仍然是一个对象,占用内存中分配的一些空间。然而,这个对象会有两个现有的引用。
// Remove the first reference to { bookName: 'The Art of Computer Programming' } object
let toRead = null
// The { bookName: 'The Art of Computer Programming' } object
// is still reachable through the second reference
// and garbage collector can't remove it, release it from memory
// Remove the second reference to { bookName: 'The Art of Computer Programming' } object
let alreadyRead = null
// All references to the { bookName: 'The Art of Computer Programming' } object
// are gone and this object is now available
// for the garbage collector to be removed
相互链接的对象或循环引用
可达性和引用的概念在对象相互链接时会失效。这也称为循环引用。这种情况发生在两个对象相互引用时。在这种情况下,垃圾收集器无法删除它们中的任何一个,因为每个对象都至少有一个引用。
// Create function that creates circular reference
function createCircularReference(obj1, obj2) {
// Interlink both objects passed as arguments
obj1.second = obj2
obj2.first = obj1
// Return new object based on the interlinked object
return {
winner: obj1,
loser: obj2
}
}
// Declare new variable and assign it the result
// of calling the createCircularReference() function
let race = createCircularReference({ name: 'Jack' }, { name: 'Spencer' })
// The value of "race" variable will be the third object
// created by interlinking the two objects
// passed to createCircularReference() function.
// These three objects are now all reachable
// because they reference each other
// and the "race" is a global variable, root
标记-清除算法
垃圾收集使用的最后一个技巧是标记-清除算法。该算法定期运行并执行一系列步骤。首先,它会获取所有现有的根并进行标记。它基本上会将其保存到内存中。接下来,它会访问从这些根开始的所有引用。它也会标记这些引用。
之后,它会再次访问已标记的对象并标记它们的引用。这个访问和标记的过程一直持续,直到所有可达的引用都被访问过。当这种情况发生时,垃圾收集器就知道哪些对象被标记了,哪些没有。
未标记的对象被视为不可达,可以安全移除。然而,这并不意味着这些对象会被立即移除。从对象被选中进行垃圾回收到实际移除,可能会存在一些时间间隔。
手动垃圾收集
除了这些工具和技巧之外,还有其他优化方法可以使您的代码运行得更流畅、更好、更快。这些优化包括分代收集、增量收集和空闲时间收集。但不包括,甚至不可能实现某种手动垃圾收集。
这是垃圾收集的一大优点。它会在后台自动运行,你无需做任何事情。但它也有缺点,因为它只能自动运行。你既无法触发或强制执行,也无法停止或阻止它。垃圾收集终将发生,你永远不知道它何时发生,但它终将发生。
结论:JavaScript 中的垃圾收集是什么以及它是如何工作的
垃圾回收是 JavaScript 开发者每天都要面对的问题之一。我希望本教程能帮助您理解 JavaScript 中的垃圾回收是什么以及它是如何工作的。如果您想了解更多关于 JavaScript 垃圾回收的知识,请查看这篇文章。
文章来源:https://dev.to/alexdevero/what-garbage-collection-in-javascript-is-and-how-it-works-4om6