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);
// …
// 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 作业结束之前不会被回收。有关详细信息,请参阅关于 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() {
// 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 语言规范 # sec-finalization-registry-objects |
浏览器兼容性
BCD 表格仅在浏览器中加载