JavaScript FinalizationRegistry
原创FinalizationRegistry
FinalizationRegistry对象允许你在某个值被垃圾回收时请求一个回调函数。
描述
FinalizationRegistry提供了一种机制,当注册到注册表中的值被回收(垃圾回收)时,请求调用一个清理回调函数。(清理回调函数有时也称为终结器。)
注意:清理回调函数不应用于关键程序逻辑。有关详细信息,请参阅关于清理回调函数的说明。
创建注册表时需要传入一个回调函数:
const registry = new FinalizationRegistry((heldValue) => {
// …
});
然后,通过调用register方法注册任何你希望有清理回调函数的值,传入该值及其一个保留值:
registry.register(target, "some value");
注册表不会对值保持强引用,因为这会违背目的(如果注册表强引用它,该值将永远不会被回收)。在JavaScript中,对象和非注册符号可以被垃圾回收,因此它们可以作为目标或令牌注册到FinalizationRegistry对象中。
如果target被回收,你的清理回调函数可能会在某个时刻被调用,并传入你为它提供的保留值(上面例子中的"some value")。保留值可以是任何你喜欢的值:基本类型或对象,甚至是undefined。如果保留值是一个对象,注册表会对其保持强引用(以便稍后可以将其传递给你的清理回调函数)。
如果你可能希望稍后取消注册已注册的目标值,则需要传入第三个值,即你将在调用注册表的unregister函数时使用的取消注册令牌。注册表只对取消注册令牌保持弱引用。
通常使用目标值本身作为取消注册令牌,这是完全可以的:
registry.register(target, "some value", target);
// …
// 稍后,如果你不再关心target,可以取消注册它
registry.unregister(target);
不过,它不必是相同的值;也可以是不同的值:
registry.register(target, "some value", token);
// …
// 稍后
registry.unregister(token);
尽可能避免使用
正确使用FinalizationRegistry需要仔细考虑,如果可能,最好避免使用它。同样重要的是,要避免依赖规范未保证的任何特定行为。垃圾回收何时、如何发生以及是否发生,取决于任何给定JavaScript引擎的实现。在一个引擎中观察到的任何行为可能在另一个引擎中、在同一引擎的不同版本中,甚至在同一引擎的相同版本的不同情况下有所不同。垃圾回收是一个难题,JavaScript引擎实现者正在不断改进和完善其解决方案。
以下是引入FinalizationRegistry的提案作者包含的一些具体要点:
垃圾回收器很复杂。如果应用程序或库依赖于垃圾回收器及时、可预测地清理WeakRef或调用终结器[清理回调函数],很可能会令人失望:清理可能比预期晚得多发生,或者根本不发生。可变性的来源包括:
- 一个对象可能比另一个对象更早被垃圾回收,即使它们在同一时间变得不可达,例如由于分代回收。
- 可以使用增量和并发技术将垃圾回收工作分散在一段时间内完成。
- 可以使用各种运行时启发式算法来平衡内存使用和响应性。
- JavaScript引擎可能持有对那些看起来不可达的事物的引用(例如,在闭包或内联缓存中)。
- 不同的JavaScript引擎可能以不同方式执行这些操作,或者同一引擎可能在不同的版本中更改其算法。
- 复杂因素可能导致对象被意外地保留很长时间,例如与某些API一起使用时。
关于清理回调函数的说明
- 开发人员不应依赖清理回调函数进行关键程序逻辑。清理回调函数可能有助于在整个程序过程中减少内存使用,但不太可能有其他用途。
- 如果你的代码刚刚向注册表注册了一个值,该目标值直到当前JavaScript作业结束才会被回收。有关详细信息,请参阅WeakRef的说明。
- 符合规范的JavaScript实现,即使进行垃圾回收,也不要求调用清理回调函数。何时以及是否调用它完全取决于JavaScript引擎的实现。当一个已注册的对象被回收时,任何相关的清理回调函数可能会被调用,或者稍后被调用,或者根本不被调用。
- 主要实现很可能会在执行过程中的某个时刻调用清理回调函数,但这些调用可能远晚于相关对象被回收的时间。此外,如果一个对象注册在两个注册表中,不能保证两个回调函数会被连续调用—一个可能被调用而另一个永远不会被调用,或者另一个可能被调用得晚得多。
- 在某些情况下,即使是通常调用清理回调函数的实现也可能不会调用它们:
- 当JavaScript程序完全关闭时(例如,在浏览器中关闭标签页)。
- 当FinalizationRegistry实例本身不再被JavaScript代码可达时。
- 如果WeakRef的目标也在FinalizationRegistry中,WeakRef的目标会在与注册表相关的任何清理回调函数被调用时或之前被清除;如果你的清理回调函数对对象的WeakRef调用deref,它将收到undefined。
构造函数
- FinalizationRegistry()
- 创建一个新的FinalizationRegistry对象。
实例属性
这些属性定义在FinalizationRegistry.prototype上,并由所有FinalizationRegistry实例共享。
- FinalizationRegistry.prototype.constructor
- 创建实例对象的构造函数。对于FinalizationRegistry实例,初始值为FinalizationRegistry构造函数。
- FinalizationRegistry.prototype[Symbol.toStringTag]
- [Symbol.toStringTag]属性的初始值为字符串"FinalizationRegistry"。此属性用于Object.prototype.toString()。
实例方法
- FinalizationRegistry.prototype.register()
- 将对象注册到注册表中,以便在/当对象被垃圾回收时获得清理回调函数。
- FinalizationRegistry.prototype.unregister()
- 从注册表中取消注册一个对象。
示例
创建新注册表
创建注册表时需要传入一个回调函数:
const registry = new FinalizationRegistry((heldValue) => {
// …
});
注册对象进行清理
然后,通过调用register方法注册任何你希望有清理回调函数的对象,传入该对象及其一个保留值:
registry.register(theObject, "some value");
回调函数永远不会被同步调用
无论你对垃圾回收器施加多大的压力,清理回调函数永远不会被同步调用。对象可能会被同步回收,但回调函数总是在当前作业完成后某个时刻被调用:
let counter = 0;
const registry = new FinalizationRegistry(() => {
console.log(`Array gets garbage collected at ${counter}`);
});
registry.register(["foo"]);
(function allocateMemory() {
// 分配50000个函数—大量内存!
Array.from({ length: 50000 }, () => () => {});
if (counter > 5000) return;
counter++;
allocateMemory();
})();
console.log("Main job ends");
// 日志输出:
// Main job ends
// Array gets garbage collected at 5001
然而,如果你在每个分配之间允许一点间隔,回调函数可能会被更早调用:
let arrayCollected = false;
let counter = 0;
const registry = new FinalizationRegistry(() => {
console.log(`Array gets garbage collected at ${counter}`);
arrayCollected = true;
});
registry.register(["foo"]);
(function allocateMemory() {
// 分配50000个函数—大量内存!
Array.from({ length: 50000 }, () => () => {});
if (counter > 5000 || arrayCollected) return;
counter++;
// 使用setTimeout使每个allocateMemory成为一个不同的作业
setTimeout(allocateMemory);
})();
console.log("Main job ends");
不能保证回调函数会被更早调用,或者是否会被调用,但有可能记录的消息的计数器值小于5000。
规范
| 规范 |
|---|
| ECMAScript® 2026 Language Specification # sec-finalization-registry-objects |
浏览器兼容性
另请参阅
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权本站发表,未经许可,不得转载。
开发学习网




