JavaScript 垃圾回收
JavaScript提供了有效的内存管理垃圾回收功能。JavaScript自动支持内存管理,并且对我们来说是不可见的。当我们创建新的对象、函数、基本类型和变量时,所有这些编程元素都会占用内存。JavaScript如何管理这些元素并清理它们呢?
在本节中,我们将讨论JavaScript内存管理的所有内部过程。我们将重点讨论以下主题:
- JavaScript中的内存生命周期
- JavaScript中的内存分配
- 不再使用时的内存释放
- JavaScript垃圾回收
- JavaScript垃圾回收中的引用概念
- 标记-清除算法
其他编程语言,如C语言,支持手动内存管理方法,如malloc()和free()。相比之下,JavaScript会自动为新对象分配内存,并在不使用时释放它们。因此,JavaScript是Web上最快的编程语言之一。
让我们了解JavaScript中的内存管理,以了解更多关于JavaScript垃圾回收的内容。
内存生命周期
每种编程语言的内存生命周期基本上是相似的。它的工作方式如下:
- 为新实例分配所需的内存
- 在调用实例时使用已分配的内存(读取、写入)
- 当内存不再使用时,释放已分配的内存。
但是,第二部分在所有编程语言中可能有所不同,这取决于它们的架构和使用方法。在低级语言(如C)中,第一步和第三步是显式的,但在高级语言(如JavaScript)中,它们大多是隐式的。
JavaScript中的内存分配
值的初始化
当在JavaScript中声明值时,它们会自动分配内存。我们不需要手动为创建的变量和对象分配内存。
让我们看一下以下示例,它解释了不同类型的变量和对象的内存分配:
const a = 200; // It will allocate the memory for a number type variable
const a = 'anystring'; // it will allocate the memory for a string type variable
const new_obj = {
a: 5,
b: null,
}; // It will allocate the memory for an object and contain values
// Just like the objects, it will also allocate memory for the array and
// contained values
const my_array = [5, null, 'anystring'];
// JavaScript also allocated memory to a function
function my_function(a) {
return a + 5;
} // It will allocate a function (which is a callable object)
// JavaScript also allocates memory to a function expressions object
anyHtmlElement.addEventListener('click', function() {
htmlElement.style.backgroundColor = 'green';
}, true);
从上面的示例中,我们可以看到JavaScript为每个对象、变量和方法分配值。
函数调用
在JavaScript中,一些函数调用可能会导致对象分配内存。例如,如果我们通过调用Date()函数来创建一个日期对象,那么它也会为日期对象分配内存。请看下面的示例:
const d = new Date(); // it will allocate the memory for a Date object
const e = document.createElement('div'); // It will allocate the memory for a DOM element
某些方法还会占用JavaScript内存空间。
const s1 = 'mystring';
const s2 = s1.substr(0, 2); // s2 is a new string
//In JavaScript, strings are immutable values
// In the case of a string, JavaScript may decide not to allocate memory,
// but it will store the range value, which is [0, 3].
const my_array1 = ['string1 string', 'nan nan'];
const my_array2 = ['newstring', 'nan nan'];
const my_array3 = my_array1.concat(my_array2);
// new array with 4 elements being
// the concatenation of my_array1 and my_array2 elements.
读取和写入值
当在JavaScript中使用这些值时,它们与为读取和写入它们分配的内存进行交互。我们可以从变量或对象属性的分配内存中读取和写入值,甚至可以将参数传递给函数。
释放不再使用的内存
当释放分配的内存时,大多数内存管理问题发生在此阶段。在这个阶段,最复杂的任务是确定特定的变量、对象或函数何时不再需要。
低级编程语言要求开发人员手动指定何时释放已分配实例的内存。
但是,高级语言如JavaScript使用自动内存管理,即垃圾回收。
垃圾回收的主要用途是自动监视和查找哪些特定的块不再使用,并对其进行回收。
然而,内存管理的自动过程完全基于近似值。因此,确定特定的内存片段是否仍然需要是无法解决的一个普遍问题。在这里,JavaScript垃圾收集器出现并有效地管理内存。
现在我们已经讨论了JavaScript中的内存管理问题。让我们继续我们的主题垃圾收集:
JavaScript垃圾回收
正如讨论过的,垃圾回收器面临的主要问题是自动找出特定的内存是否会被利用。在这种情况下,垃圾回收器对此问题实施了限制。为了解决这个问题,JavaScript使用了引用的概念。让我们了解一下引用的概念:
JavaScript垃圾回收中的引用概念
对于自动内存管理,JavaScript依赖于引用的概念。在这个概念中,在内存管理上下文中,如果其他对象在应用程序中稍后被访问过,无论是隐式还是显式访问,就被认为是一个对象对另一个对象的引用。例如,该对象将有一个对其原型的引用,这将是一个隐式引用,以及对其属性值的引用,这将是一个显式引用。
在这种情况下,对象的概念比JavaScript中的常规对象更广泛,它还包含了对象的范围。
引用计数垃圾回收
JavaScript垃圾收集器遵循一种计算引用的算法。该算法确定一个对象是否还需要确定一个对象是否还有其他引用它的对象。如果指向它的引用为零,它就被判断为垃圾。
让我们通过下面的示例来理解它:
let a = {
x: {
y: 5
}
};
// Here, two objects are created; x and y. where x is referenced by the other y as one of its properties.
// The other is referenced by virtue of being assigned to the 'a variable.
// Here, none of the given objects can be garbage-collected.
let a = b; // The 'b' variable is the second thing that has a reference to the object.
a = 5; // Here, the object that was originally in 'a' will have a unique reference
// embodied by the 'b' variable.
let z = b.x; // Reference to 'x' property of the object.
// now, z object will have two references: one as a property,
// the other as the 'z' variable.
b = 'chrome'; // The object that was originally in 'b' has now zero
// references to it. It can be garbage-collected.
// However its 'a' property is still referenced by
// the 'z' variable, so it cannot be freed.
z = null; // The 'a' property of the object originally in a
// has zero references to it. It can be garbage collected.
限制(循环引用)
JavaScript garbage collector has limitations when it finds circular references. For example, consider the below example:
function myFunction() {
const a = {};
const b = {};
a.x = b; // a references b
b.y = a; // b references a
return 'someString';
}
myFunction();
从上面的示例中,我们创建了两个相互引用的对象,形成了一个引用循环;这种情况被称为循环引用。当函数myFunction()执行完毕时,它将超出范围。此时它们将不再需要,但是它们的分配内存将被回收。因此,垃圾收集器不会清理它们的分配。
然而,垃圾收集器的引用计数算法将无法将它们识别为可回收对象,因为它们各自都有一个引用;这就是为什么它们都不会被标记为垃圾收集的原因。循环引用问题是应用程序中内存泄漏的原因之一。
IE 6和IE 7使用引用计数垃圾收集器,但其他现代引擎都不使用引用计数垃圾收集器来防止由于循环引用导致的内存泄漏。
标记-清除算法
标记-清除算法减少了“一个对象不再使用”的算法的使用;相反,它专注于“一个对象不可达”的方法。
这种方法侧重于根(对象集合)。根是JavaScript中的全局对象。在这种方法中,垃圾收集器从根(全局对象)开始,找到从这些根指向的对象。它引用了这些根指向的所有对象。因此,它找到了所有可达和不可达的对象。
这个算法是对引用计数算法的改进。它将一个引用都没有的对象视为不可达。因此,在这个算法中,如果一个对象有循环引用,它不是真正不可达的。
截至2012年,所有现代浏览器都遵循标记-清除算法来进行垃圾回收。在过去几年中,JavaScript通过实现这种方法而不是其本来的方法“一个对象不再需要”来进行了改进。
在第一个示例中,在函数调用后没有两个对象引用任何资源。因此,它们被垃圾收集器视为不可达,并且它们的内存可以被回收。
限制:
在应用程序中可能有时候需要方便地手动指定要释放的内存。要释放对象的内存,我们需要手动将其明确设置为不可达。
我们不能手动触发JavaScript垃圾收集器。
总结
JavaScript提供了高效的内存管理垃圾回收。JavaScript自动支持内存管理,并对我们来说不可见。我们不能手动触发JavaScript垃圾收集器。