TypeScript 中有析构函数吗?

79
TypeScript 中有析构函数吗?如果没有,我该如何删除一个对象? 我尝试过 destructor()~ClassName(),但都没有起作用。
3个回答

91

JavaScript使用垃圾回收机制,在对象不再被引用时自动删除它们。没有析构函数或终结器的概念。

你无法观察到垃圾回收器何时删除对象,也无法预测。


7
如果一个定时器、回调函数或事件监听器仍然连接在要删除的对象上,那么垃圾回收可能会被阻止。如果回调函数/监听器仍然存在但对象已不存在,则也可能会发生错误。如果想要绝对确定,可以编写自己的析构方法并在删除对象之前调用它。 - Kokodoko
39
这不是正确答案。Gábor Lupák可能已经知道那里会发生垃圾收集。即使有垃圾收集器,在应用程序执行期间仍然需要有时释放一些长时间存活的引用,以便这些引用资源能够符合垃圾收集的条件,或者至少没有内存泄漏。我的建议是创建一个接口(例如:interface Releasable {}),并定义一个名为release()的方法,还可以使用一个名为released的标志来防止多次运行release()。 - asiby
11
@asiby - 那可能是一个合适的解决方法,但该解决方案不是一个析构函数。这个答案是正确的- Javascript没有析构函数。 - Tom Mettam
5
最后,析构函数不需要与GC有任何关联才能工作。严格来说,析构函数在一个对象的生命周期结束时被调用,而不是当一个对象被收集时被调用(无论如何,这都必须在析构函数被调用之后)。因此,我们不需要将析构函数的调用与GC的操作同步。实际上,析构函数可以在收集之前的任何时刻触发。如果一种语言的语义支持静态生命周期,编译器可以在对象的生命周期到期之前的某个时刻(例如当它离开程序范围时)合成一个obj.destruct() - James M. Lay
1
你是错误的。如果你正在监听事件或被监听事件,唯一允许垃圾回收的方法就是取消事件监听器。这个方法是通过调用析构函数来实现的,有时会调用其他引用类实例的析构函数,如果适当的话,也是为了停止事件监听器。此外,在NodeJS中,观察GC操作现在是可能的。 - oxygen
显示剩余4条评论

14

从ES2021开始,规范中添加了finalizers。

要使用该功能,您需要创建一个FinalizationRegistry,它会在任何相关对象被垃圾回收时通知您。

您可以像这样使用它:

const reg = new FinalizationRegistry((id: number) => {
  console.log(`Test #${id} has been garbage collected`);
});

class Test{
  id: number;
  constructor(id: number){
    this.id = id;
    reg.register(this, this.id);
    //                 ^^^^^^^--- This is the "testament", whatever value, which will be passed to the finalization callback
  }
}

{
  const test1 = new Test(1);
  const test2 = new Test(2);
}

请注意,当回调被调用时,对象已经被垃圾回收;只有它的“遗嘱”(或者按照MDN的说法,“保留值”)会被传递给终结器。

如果您需要在终结器中访问对象的某些属性,您可以将它们存储在遗嘱中,在这种情况下,遗嘱可能会(但不一定会)在原始对象之后被垃圾回收:

interface TestTestament{
  id: number,
  intervalid: ReturnType<typeof setInterval>
}

const reg = new FinalizationRegistry((testament: TestTestament) => {
  console.log(`Test #${testament.id} has been garbage collected`);
  clearInterval(testament.intervalid);
});

class Test{
  private testament: TestTestament;
  constructor(id: number){
    this.testament = {
      id,
      intervalid: setInterval(() => {
        console.log(`Test interval #${id}`);
      }, 1000)
    };

    reg.register(this, this.testament);
  }
}

{
  const test1 = new Test(1);
  const test2 = new Test(2);
}

请注意,规范并不保证垃圾回收发生的时间,因此如果对象一直存在于内存中,finalizer甚至可能不会被调用。

1
这个机制是可选的,不能保证一定会起作用,这是正确的吗?Mozilla文档中说:“即使是进行垃圾回收的符合JavaScript实现,也不需要调用清理回调。何时以及是否调用完全取决于JavaScript引擎的实现。当注册对象被回收时,任何与之相关的清理回调可能会被调用,或者在之后的某个时间被调用,或者根本不被调用。”在这种情况下,当您想要防止自管理引用的内存泄漏时,它是完全无用的。 - ChrisoLosoph
@ChrisoLosoph 很不幸,这是真的。但据我所知,回调函数将在大多数主要浏览器中运行。 - FZs

-19

你实际上可以

    class MyClass {
        constructor(input1, input2){
             this.in1 = input1;
             this.in2 = input2;
         }

    }
    let myObject = {};


    try {
         myObject = {
             classHandler: new MyClass('1','2')
         }
    } catch (e) {
    } finally {
        delete myObject.classHandler
        // garbageCollect
        if (global.gc) {global.gc()}
    }


    

2
你能详细说明一下这是如何工作的吗?我理解中 delete 运算符只是删除了对该对象的引用,垃圾回收仍然像往常一样不可预测。 - John Montgomery
1
截至2019年,在JavaScript中无法显式或以编程方式触发垃圾回收。但是,这个脚手架提供了一种可预测的方式来考虑对象是否为垃圾。 - Thomas Thornier
即使作为一种解决方法,这也并不简单。假设类设置了一个间隔计时器,调用其中的一个方法。这只是许多事情中的其中之一,可以防止对象在垃圾收集期间被销毁。关于delete经常存在很多困惑,特别是对于JavaScript的初学者,我担心这个答案会延续这种情况。**delete与垃圾回收无关。**实践中,似乎实现可预测的资源清理的方法是添加一个方法(例如destroy),然后在应用程序不再需要该对象时调用它。 - jacobq
2
存在一个概念上的误解。析构函数的概念与垃圾回收只有间接关联,因为某些语言中GC可能是调用它的人,但这并不一定是真实情况,并且在GC之前一个实例可能被处置或调用其析构函数,而与GC没有任何关系。一些语言确实将其“析构函数”的概念与GC联系起来,但在实践中,IDisposable的实现者具有两种类型的析构函数,其中一种可以确定性地使用,另一种则不能。但这只是特定实现,而不是通用概念。 - Trinidad
1
@ThomasThornier,“析构函数”并不像JS中的“delete”一样是删除引用的方法。它是由类定义的一种方法,在对象被垃圾回收时自动调用。使用该语言的人会自己编写析构函数,并通常用它来进行清理(关闭连接、删除临时文件或类似操作),以便在垃圾回收发生时执行。 - Andrew Allaire
显示剩余6条评论

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