FinalizationRegistry
Baseline 广泛可用 *
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);
// …
// some time later, if you don't care about `target` anymore, unregister it
registry.unregister(target);
然而,它不必是相同的值;可以是不同的值。
registry.register(target, "some value", token);
// …
// some time later
registry.unregister(token);
尽可能避免使用
正确使用 FinalizationRegistry 需要仔细考虑,如果可能,最好避免使用。同样重要的是要避免依赖规范未保证的任何特定行为。垃圾回收何时、如何以及是否发生,取决于任何给定 JavaScript 引擎的实现。您在一个引擎中观察到的任何行为,在另一个引擎、同一引擎的另一个版本,甚至在同一版本引擎的稍有不同的情况下,都可能不同。垃圾回收是一个棘手的问题,JavaScript 引擎的实现者正在不断改进和优化他们的解决方案。
以下是引入 FinalizationRegistry 的提案中作者包含的一些具体要点:
垃圾回收器非常复杂。如果应用程序或库依赖于 GC 来及时、可预测地清理 WeakRef 或调用终结器[清理回调],它很可能会感到失望:清理可能比预期晚得多,或者根本不发生。造成这种不确定性的原因包括:
- 即使两个对象同时变得不可达,一个对象也可能比另一个对象更早地被垃圾回收,例如,由于分代收集。
- 垃圾回收工作可以使用增量和并发技术来分时完成。
- 可以使用各种运行时启发式方法来平衡内存使用和响应能力。
- JavaScript 引擎可能持有看起来似乎不可达的对象的引用(例如,在闭包或内联缓存中)。
- 不同的 JavaScript 引擎可能以不同的方式执行这些操作,或者同一个引擎在其版本之间可能会更改其算法。
- 复杂的因素可能导致对象被意外地长时间保留,例如与某些 API 一起使用。
关于清理回调的注意事项
- 开发者不应依赖清理回调来执行关键程序逻辑。清理回调可能有助于在程序运行过程中减少内存使用,但除此之外可能不太有用。
- 如果您刚刚将一个值注册到注册表,该目标将不会在当前 JavaScript 任务结束前被回收。有关详细信息,请参阅关于 WeakRefs 的注意事项。
- 符合标准的 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() {
// Allocate 50000 functions — a lot of memory!
Array.from({ length: 50000 }, () => () => {});
if (counter > 5000) return;
counter++;
allocateMemory();
})();
console.log("Main job ends");
// Logs:
// 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() {
// Allocate 50000 functions — a lot of memory!
Array.from({ length: 50000 }, () => () => {});
if (counter > 5000 || arrayCollected) return;
counter++;
// Use setTimeout to make each allocateMemory a different job
setTimeout(allocateMemory);
})();
console.log("Main job ends");
不能保证回调会被更早调用,或者是否会被调用,但日志消息的计数器值可能小于 5000。
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # sec-finalization-registry-objects |
浏览器兼容性
加载中…