相当于JavaScript'对象模型中的析构函数

Equivalent of destructors in JavaScript's object model

本文关键字:析构函数 对象模型 JavaScript 相当于      更新时间:2023-09-26

由于我过去已经处理过JavaScript的古怪的"对象模型",所以我假设没有析构函数这样的东西。我的搜索都没有成功,所以你们是我最后的希望了。如何在实例销毁时执行程序?

MDN是一个很好的JS资源。不,没有什么比在对象停止时调用函数更好的了。

FinalizationRegistry可能就是您需要的。它不是析构函数,但是一旦对象被垃圾收集,它就执行一个函数。在任何情况下,这是我希望我发现当我第一次来到这里:)

除了已经提到的为非确定性终结提供支持的FinalizationRegistry之外,还有一项提案(截至2023年3月撰写本文时)正在向该语言添加一个确定性的、基于作用域的清理构造:显式资源管理。它可能在2023年或2024年达到标准。

语法已经经历了多次迭代,但是考虑到它已经经过了阶段3的审查,现在不太可能有太大的变化。下面是一个包装URL.createObjectURL的演示:

class BlobURL {
    #url = null;
    constructor(blob) {
        this.#url = URL.createObjectURL(blob);
    }
    toString() {
        if (this.#url == null)
            throw new TypeError("URL was already revoked");
        return this.#url;
    }
    valueOf() {
        return this.#url;
    }
    [Symbol.dispose]() {
        URL.revokeObjectURL(this.#url);
        this.#url = null;
    }
}
const doIFeelLucky = (url) => {
    // imagine some process here that
    // uses the URL, but may throw
    if (6 * Math.random() < 1)
        throw new Error("seems I don't");
};
const processBlob = (blob) => {
    using url = new BlobURL(blob);
    doIFeelLucky(url);
    // `url` is revoked when the function returns or throws
};

所有权转移有点笨拙,因为它需要创建一个DisposableStack来管理它:

const processBlobAndReturnItsURL = (blob) => {
    using stack = new DisposableStack();
    const url = stack.use(new BlobURL(blob));
    doIFeelLucky(url);   // if this throws, `url` will be revoked
    stack.move();        // release ownership
    return url;          // `url` will be returned without being revoked
}

其他缺点:不支持解构(尽管在提案进行时有许多建议允许它),并且构造的表达能力不足以像Python上下文管理器那样执行异常捕获(尽管这是否真的有意义是有争议的)。尽管如此,它还是比我们现在使用的finally稍微好一些。

最近,这个链接更多地用于回答这个问题。最重要的部分:

截至2012年,所有现代浏览器都提供了标记和清除功能垃圾收集器。

循环不再是问题

在上面的第一个例子中,函数调用返回后,两个对象不再被任何可访问的资源引用从全局对象。因此,他们将被发现是遥不可及的由垃圾回收器回收它们分配的内存。

限制:手动释放内存

有些时候手动决定什么时候会很方便以及释放了哪些记忆。为了释放一个人的记忆对象,则需要显式地使其不可访问。

所以就循环引用而言,de[con]构造函数并不是真正需要的。


我想到了一个很酷的技巧,如果你有循环引用,你想要轻松地手动控制对解构…

class Container {
  constructor() {
    this.thisRef = [ this ];
    this.containee = new Containee({ containerRef: this.thisRef });
  }
  //Note: deconstructor is not an actual JS thing/keyword.
  deconstructor() {
    //Have to delete `this.thisRef[0]` and not `this.thisRef`, in
    //order to ensure Containee's reference to Container is removed.
    delete this.thisRef[0];
  }
  doSomething() {
  }
}
class Containee {
  constructor({ containerRef }) {
    //Assumption here is, if the Container is destroyed, so will the Containee be
    //destroyed. No need to delete containerRef, no need for a
    //deconstructor function!
    this.containerRef = containerRef;
  }
  someFunc() {
    this.containerRef[0].doSomething();
  }
}
let c = new Container();
...
//No cyclic references!
c.deconstructor();

所以在这里,Containee类不是存储对Container实例的直接引用,而是存储对一个大小为1的数组的引用,该数组包含Container引用,然后容器实例本身可以从中删除自己。包含引用的数组由Container管理。

但是,这不是真正需要的,因为所有现代浏览器中的垃圾收集都是标记-清除,并且可以处理循环引用。