什么是 JavaScript 垃圾回收?对于 Web 程序员来说,为了编写更好的代码,了解 JavaScript 垃圾收集有什么重要的?
Eric Lippert 不久前就该主题写了一篇detailed blog post(另外将其与 VBScript 进行了比较)。更准确地说,他写了关于 JScript 的文章,它是 Microsoft 自己的 ECMAScript 实现,尽管与 JavaScript 非常相似。我想您可以假设 Internet Explorer 的 JavaScript 引擎的绝大多数行为都是相同的。当然,实现会因浏览器而异,但我怀疑您可以采用一些通用原则并将它们应用于其他浏览器。
从该页面引用:
JScript 使用非分代标记和清除垃圾收集器。它的工作原理是这样的:“范围内”的每个变量都称为“清道夫”。 scavenger 可以指一个数字、一个对象、一个字符串等等。我们维护一个清道夫列表——当变量进入作用域时,它们被移到 scav 列表中,当它们超出作用域时,它们被移出 scav 列表。垃圾收集器时不时地运行。首先,它在每个对象、变量、字符串等上放置一个“标记”——GC 跟踪的所有内存。 (JScript 内部使用了 VARIANT 数据结构,并且该结构中有很多额外的未使用位,因此我们只设置其中一个。)其次,它清除了清除器上的标记和清除器引用的传递闭包。因此,如果 scavenger 对象引用了 nonscavenger 对象,那么我们会清除 nonscavenger 上的位,以及它所引用的所有内容。 (我使用的“闭包”一词的含义与我之前的帖子不同。)此时,我们知道所有仍然标记的内存都是分配的内存,任何范围内变量的任何路径都无法访问。所有这些对象都被指示自行拆除,这会破坏任何循环引用。
垃圾收集的主要目的是让程序员不必担心他们创建和使用的对象的内存管理,当然有时也无法避免 - 至少对垃圾收集的工作原理有一个粗略的了解总是有益的.
历史记录:答案的早期版本对 delete
运算符的引用不正确。在 JavaScript the delete
operator removes a property from an object 中,与 C/C++ 中的 delete
完全不同。
当涉及 DOM 对象时,请注意循环引用:
Memory leak patterns in JavaScript
请记住,只有在没有对对象的活动引用时才能回收内存。这是闭包和事件处理程序的常见缺陷,因为一些 JS 引擎不会检查内部函数中实际引用了哪些变量,而只会保留封闭函数的所有局部变量。
这是一个简单的例子:
function init() {
var bigString = new Array(1000).join('xxx');
var foo = document.getElementById('foo');
foo.onclick = function() {
// this might create a closure over `bigString`,
// even if `bigString` isn't referenced anywhere!
};
}
只要事件处理程序在附近,幼稚的 JS 实现就无法收集 bigString
。有几种方法可以解决这个问题,例如在 init()
的末尾设置 bigString = null
(delete
不适用于局部变量和函数参数:delete
从对象中删除属性,并且变量对象不可访问- 如果您尝试删除局部变量,ES5 在严格模式下甚至会抛出 ReferenceError
!)。
如果您关心内存消耗,我建议尽可能避免不必要的关闭。
来自博客的好报价
DOM 组件是“垃圾收集”的,JScript 组件也是如此,这意味着如果您在任一组件中创建一个对象,然后失去对该对象的跟踪,它最终会被清理掉。
例如:
function makeABigObject() {
var bigArray = new Array(20000);
}
当您调用该函数时,JScript 组件会创建一个可在该函数内访问的对象(名为 bigArray)。但是,一旦函数返回,您就“失去了对 bigArray 的跟踪”,因为无法再引用它。好吧,JScript 组件意识到您已经忘记了它,所以 bigArray 被清理了——它的内存被回收了。同样的事情也适用于 DOM 组件。如果您说 document.createElement('div')
或类似名称,则 DOM 组件会为您创建一个对象。一旦你不知何故忘记了那个对象,DOM 组件就会清理相关的。
据我所知,当没有对对象的引用时,JavaScript 的对象会定期被垃圾收集。这是自动发生的事情,但如果您想了解更多关于它的工作原理,在 C++ 级别,看看 WebKit 或 V8 source code
但是,通常您不需要考虑它,但在较旧的浏览器中,如 IE 5.5 和 IE 6 的早期版本,也许还有当前版本,闭包会创建循环引用,如果未选中,最终会耗尽内存。在我所说的关于闭包的特殊情况下,它是当你添加一个对 dom 对象的 JavaScript 引用,以及一个指向 JavaScript 对象的 DOM 对象的引用时。基本上它永远不会被收集,并最终导致操作系统在循环创建崩溃的测试应用程序中变得不稳定。实际上,这些泄漏通常很小,但为了保持代码干净,您应该删除对 DOM 对象的 JavaScript 引用。
通常使用 delete 关键字来立即取消引用大对象(如您收到的 JSON 数据)并使用它完成任何您需要做的事情是一个好主意,尤其是在移动 Web 开发中。这会导致 GC 的下一次扫描删除该对象并释放其内存。
mark-and-sweep
样式算法take care of this。
垃圾回收 (GC) 是一种自动内存管理形式,通过删除不再需要的对象。
任何处理内存的进程都遵循以下步骤:
1 - 分配你需要的内存空间
- 做一些处理
- 释放这个内存空间
有两种主要算法用于检测不再需要的对象。
引用计数垃圾回收:该算法将“不再需要一个对象”的定义简化为“一个对象没有其他对象引用它”,如果没有引用指向它,则该对象将被删除
标记和扫描算法:将每个对象连接到根源。任何对象都不会连接到根或其他对象。此对象将被删除。
目前大多数现代浏览器使用第二种算法。
“在计算机科学中,垃圾收集 (GC) 是一种自动内存管理形式。垃圾收集器,或者只是收集器,试图回收垃圾,或者应用程序永远不会再次访问或改变的对象使用的内存。”
所有 JavaScript 引擎都有自己的垃圾收集器,它们可能会有所不同。大多数时候你不必与他们打交道,因为他们只是做他们应该做的事。
编写更好的代码主要取决于您对编程原理、语言和特定实现的了解程度。
引用类型不会将对象直接存储到分配给它的变量中,因此下面示例中的对象变量实际上并不包含对象实例。相反,它持有指向内存中对象所在位置的指针(或引用)。
var object = new Object();
如果将一个引用类型变量分配给另一个,则每个变量都会获得指针的副本,并且两者仍然引用内存中的同一对象。
var object1 = new Object();
var object2 = object1;
https://i.stack.imgur.com/CTFQt.png
JavaScript 是一种垃圾收集语言,所以当你使用引用类型时,你真的不需要担心内存分配。但是,最好取消引用不再需要的对象,以便垃圾收集器可以释放该内存。最好的方法是将对象变量设置为空。
var object1 = new Object();
// do something
object1 = null; // dereference
在使用数百万个对象的大型应用程序中,取消引用对象尤其重要。
来自面向对象 JavaScript 的原则 - NICHOLAS C. ZAKAS
什么是 JavaScript 垃圾回收?
检查this
对于 Web 程序员来说,为了编写更好的代码,了解 JavaScript 垃圾收集有什么重要的?
在 Javascript 中,您不关心内存分配和释放。整个问题都需要 Javascript 解释器。 Javascript 中仍然可能存在泄漏,但它们是解释器的错误。如果您对此主题感兴趣,可以在 www.memorymanagement.org 中阅读更多内容
在 Windows 上,您可以使用 Drip.exe 查找内存泄漏或检查您的免费内存例程是否有效。
真的很简单,只要输入一个网站网址,你就会看到集成的IE渲染器的内存消耗。然后点击刷新,如果内存增加,你在网页的某个地方发现了内存泄漏。但这对于查看释放内存的例程是否适用于 IE 也非常有用。
在 javascript 中,垃圾收集是不确定的,何时清除对象,或者是否会清除。这适用于强引用的对象。强引用对象受到垃圾收集的保护。
在 ES12 之后,可以执行以下实现来检查对象何时被垃圾回收。
要了解更多关于 javascript 中垃圾收集的信息,您可以使用 ES12 之后提供的 Finalisers。
let a = new Array(200).fill(true);
构造finaliaser
const cleanup = new FinalizationRegistry(key => {
// your code here
});
cleanup.register(a, 'wewew');
对象“a”现在不可访问,垃圾回收后将发生终结器回调
delete
不正确;例如,在第一个示例中,您应该先通过window.removeEventListener()
删除事件侦听器,而不是delete foo
,然后使用foo = null
覆盖变量;在 IE 中,如果foo
是全局的,delete window.foo
(但不是delete foo
)也可以工作,但即便如此它也不会在 FF 或 Opera 中delete
是一元运算符(表达式),而不是语句(即:delete 0, delete 0, delete 3
)。用表达式语句表示时看起来像语句。