JavaScript对象何时被销毁?

27
在C++中,可以显式定义构造函数和析构函数,然后在构造函数/析构函数中使用cout<< "C or D Called"来确定确切的位置。
然而,在JavaScript中,我该如何知道对象何时被销毁?下面的示例是我关心的情况。
我正在超时内部调用一个函数,并且想知道当计时器运行并等待再次调用时,对象是否仍然存在。 用户单击调用控件
// Calls  Control

控件调用消息

var message_object = new Message( response_element );

消息调用效果

new Effects().fade( this.element, 'down', 4000 );
message_object.display( 'empty' );

效果

/**
 *Effects - build out as needed
 *  element - holds the element to fade
 *  direction - determines which way to fade the element
 *  max_time - length of the fade
 */

var Effects = function(  ) 
{
    this.fade = function( element, direction, max_time ) 
    {
        element.elapsed = 0;
        clearTimeout( element.timeout_id );
        function next() 
        {
            element.elapsed += 10;
            if ( direction === 'up' )
            {
                element.style.opacity = element.elapsed / max_time;
            }
            else if ( direction === 'down' )
            {
                element.style.opacity = ( max_time - element.elapsed ) / max_time;
            }
            if ( element.elapsed <= max_time ) 
            {
                element.timeout_id = setTimeout( next, 10 );
            }
        }
        next();
    }
};

6
JavaScript 垃圾回收 - https://dev59.com/Z3RA5IYBdhLWcg3wsgBPJavaScript 垃圾回收器负责自动管理内存分配和释放。它会在一段时间后检查哪些对象不再被引用,然后将其从内存中删除以释放空间。垃圾回收器使用"标记-清除"算法,首先标记所有活动对象,然后清除未被标记的对象。此外,它还使用"引用计数"技术来跟踪对象的引用次数,当某个对象的引用计数为零时,也会被当作垃圾回收。JavaScript 引擎通常具有优化策略,例如分代垃圾回收,根据对象的生命周期划分多个代,并在较年轻的代中更频繁地执行垃圾回收操作。这些优化可以提高性能并减少内存占用。虽然垃圾回收是 JavaScript 的一个重要特性,但它也可能导致性能问题。例如,在大型应用程序中,垃圾回收可能会占用大量的 CPU 时间,从而影响应用程序的响应性能。为了避免这种情况,开发人员可以采取措施来减少垃圾回收的频率,例如使用缓存池或避免创建大量临时对象。 - Brandon Boone
一般来说是的,有关释放对象的具体条件的讨论请参见上面的链接。 - Jakub Hampl
你担心哪个对象的寿命? - Phrogz
不,你的想法是相反的。控制(Control)有一个对信息(Message)的引用,但反过来不一定成立。除非Effects有一些方式可以访问Message -- 除非Message调用了它或向其传递了对自身的引用-- Message就会消失。是的,this.element保持不变,但围绕它的对象却消失了。 - zetlen
5个回答

28

我认为将对象销毁简化为内存垃圾回收是一个极具误导性的想法,因为问题不仅在于释放内存。

析构函数负责释放其他资源,例如文件描述符或事件监听器,这些资源不能被垃圾回收自动处理。在这种情况下,必须使用析构函数在释放内存之前解开状态,否则会造成资源泄漏。

在这种情况下,析构函数并非一种一流的概念,无论它们需要被显式调用还是在对象变得不可访问后隐式调用。

最好的方法是适当地记录模块的文档,如果需要使用析构函数,则强调资源泄漏情况下未能使用。


1
这是非常重要的答案。实际上,所有关于JavaScript销毁的参考都围绕着内存管理,而这根本不应该是这样的。我想将其点赞到Google JavaScript销毁的首页。 - Erik Aronesty
比我当初自以为是的初级程序员时的答案好多了。 - zetlen

27

编辑于2020年:@BuffyG的这个答案比我下面的旧答案更准确和有用。对象销毁不仅涉及内存泄漏,现代JavaScript中没有我提到的任何模式。

JS对象本身并没有析构函数。

JavaScript对象(以及基本类型)在变得不可访问时进行垃圾回收,也就是当当前执行上下文中没有可能引用它们时。JavaScript运行时必须持续监控这种情况。因此,除非使用delete关键字来移除某些东西,否则其销毁过程就有点隐蔽。一些浏览器不擅长检测闭包作用域中留下的引用(我在说你,雷德蒙德),这就是为什么经常看到在函数末尾将对象设置为null,以确保在IE中释放内存。


16
JS对象在变得无法访问后才会被自动垃圾回收(您无法控制何时回收)。而在C++中,对象在变得不可访问时立即被销毁。 - Gabe
2
@Gabe 在C++中,对象在你显式删除它们时被销毁,无论它们是否在其他地方可访问(实际上,从技术上讲,你必须能够访问它们才能删除它们)。它们不会在变得不可访问的时候(或之后)自动销毁。 - Jason C
3
@JasonC: 抱歉,我在谈论C ++的“自动”内存管理,使用RAII、autoptr等技术实现。 - Gabe
3
如果你不使用new(就像任何合理的C ++程序员一样),它们就是安全的。请参见https://dev59.com/_3RC5IYBdhLWcg3wK9yV。 - user1804599
显示剩余9条评论

4
ECMAscript没有任何动态内存管理。垃圾回收器会处理脚本中需要的所有内存。因此,实际上问题应该更像是:"垃圾回收器如何知道何时可以释放对象的内存"?
简单地说,大多数GC会查找是否有任何活跃引用。这可能是由于父上下文对象、原型链或对给定对象的任何直接访问。在您的特定实例中,每当执行setTimeout时,它都会调用next(),后者将关闭.fade()父上下文,并且.face()函数反过来保持对Effects函数(上下文)的闭包。
这意味着只要有对setTimeout的调用,整个构造就会被保存在内存中。
您可以通过将变量/引用设置为null来帮助旧式的GC实现提前或完全收集一些东西,但是现代实现对此类事情非常聪明。您实际上不必关心"对象/引用生命周期"之类的事情。

垃圾回收是昂贵的,因此解释器通常会“懒惰”地执行它。废弃的数据可能会在内存中存放相当长的时间,直到垃圾回收器最终运行。当它运行时,会“触及”大量数据,可能会引起许多页面错误,必须由操作系统的虚拟内存管理器处理。(不同的实现采取了不同的方法来减轻这种副作用。) - Mike Robinson

0

另一个重要的考虑因素是数据结构中的循环引用: "A指向B,B指向A,没有任何人再指向它们中的任何一个。" 理论上,这可能会导致A和B都被视为不可收集,从而导致内存泄漏。

该主题已在此处讨论,并进行了一些相当新的更新:

是否可能在JavaScript中创建“弱引用”?

其中讨论的策略是“削弱”A和B之间的一个引用,以便垃圾回收器知道它可以被打破,从而最终导致它们被收回...或者在内存不足的情况下被“窃取”。

当然,也有可能从纪律中获益。如果您知道自己不再使用某些内容,请编写一个将其各种引用字段设置为Null的例程,然后再将其放弃给垃圾回收器...“保持整洁。”


自从JavaScript语言变得如此重要以来,它的垃圾回收策略已经有了很大的进步。当你学习这个主题的文本时,请确保它们是最近的。


0

有一个实验性的 Firefox 和 Chrome 函数 window.requestIdleCallback(),当浏览器处于空闲状态时会回调。这可以用来模拟类实例析构函数。

几乎相同的效果可以通过以下代码获得:

setTimeout(function()
    {
    // Simulate destructor here
    },0);

这将设置一个自动解除的超时,当当前的JavaScript脚本完成(并且主事件循环恢复)时,它将完成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接