FinalizationRegistry
FinalizationRegistry
对象允许你在值被垃圾收集时请求回调。
¥A FinalizationRegistry
object lets you request a callback when a value is garbage-collected.
描述
¥Description
FinalizationRegistry
提供了一种方法,当在注册表中注册的值被回收(垃圾收集)时,请求在某个时刻调用清理回调。(清理回调有时称为终结器。)
¥FinalizationRegistry
provides a way to request that a cleanup callback get called at some point when a value registered with the registry has been reclaimed (garbage-collected). (Cleanup callbacks are sometimes called finalizers.)
注意:清理回调不应用于基本的程序逻辑。详情请参见 关于清理回调的注意事项。
¥Note: Cleanup callbacks should not be used for essential program logic. See Notes on cleanup callbacks for details.
你创建注册表并传入回调:
¥You create the registry passing in the callback:
const registry = new FinalizationRegistry((heldValue) => {
// …
});
然后,你可以通过调用 register
方法来注册你想要清理回调的任何值,并传入该值和它的保留值:
¥Then you register any value you want a cleanup callback for by calling the register
method, passing in the value and a held value for it:
registry.register(target, "some value");
注册表不会保留对该值的强引用,因为这会违背目的(如果注册表强烈保留该值,则该值将永远不会被回收)。在 JavaScript 中,对象和 非注册符号 是垃圾可收集的,因此它们可以在 FinalizationRegistry
对象中注册为目标或令牌。
¥The registry does not keep a strong reference to the value, as that would defeat the purpose (if the registry held it strongly, the value would never be reclaimed). In JavaScript, objects and non-registered symbols are garbage collectable, so they can be registered in a FinalizationRegistry
object as the target or the token.
如果 target
被回收,你的清理回调可能会在某个时刻使用你为其提供的保留值(上面的 "some value"
)被调用。持有价值可以是你喜欢的任何值:一个基元或一个对象,甚至是 undefined
。如果保存的值是一个对象,注册表会保留对它的强引用(以便稍后可以将其传递给清理回调)。
¥If target
is reclaimed, your cleanup callback may be called at some point with the held value you provided for it ("some value"
in the above). The held value can be any value you like: a primitive or an object, even undefined
. If the held value is an object, the registry keeps a strong reference to it (so it can pass it to your cleanup callback later).
如果你稍后可能想要取消注册已注册的目标值,则可以传递第三个值,该值是稍后在调用注册表的 unregister
函数取消注册该值时将使用的取消注册令牌。注册表仅保留对注销令牌的弱引用。
¥If you might want to unregister a registered target value later, you pass a third value, which is the unregistration token you'll use later when calling the registry's unregister
function to unregister the value. The registry only keeps a weak reference to the unregister token.
使用目标值本身作为注销令牌是很常见的,这很好:
¥It's common to use the target value itself as the unregister token, which is just fine:
registry.register(target, "some value", target);
// …
// some time later, if you don't care about `target` anymore, unregister it
registry.unregister(target);
不过,它不必是相同的值;它可以是不同的:
¥It doesn't have to be the same value, though; it can be a different one:
registry.register(target, "some value", token);
// …
// some time later
registry.unregister(token);
尽可能避免
¥Avoid where possible
正确使用 FinalizationRegistry
需要仔细考虑,并且最好尽可能避免。避免依赖规范未保证的任何特定行为也很重要。何时、如何以及是否发生垃圾收集取决于任何给定 JavaScript 引擎的实现。你在一个引擎中观察到的任何行为在另一个引擎、同一引擎的另一个版本中甚至在同一引擎的同一版本的情况下都可能有所不同。垃圾收集是 JavaScript 引擎实现者不断完善和改进其解决方案的一个难题。
¥Correct use of FinalizationRegistry
takes careful thought, and it's best avoided if possible. It's also important to avoid relying on any specific behaviors not guaranteed by the specification. When, how, and whether garbage collection occurs is down to the implementation of any given JavaScript engine. Any behavior you observe in one engine may be different in another engine, in another version of the same engine, or even in a slightly different situation with the same version of the same engine. Garbage collection is a hard problem that JavaScript engine implementers are constantly refining and improving their solutions to.
以下是作者在介绍 FinalizationRegistry
的 proposal 中包含的一些具体观点:
¥Here are some specific points included by the authors in the proposal that introduced FinalizationRegistry
:
垃圾收集者 很复杂。如果应用或库依赖 GC 及时、可预测地清理 WeakRef 或调用终结器 [清理回调],则可能会感到失望:清理工作可能比预期晚得多,或者根本不进行。变异性的来源包括:
¥Garbage collectors are complicated. If an application or library depends on GC cleaning up a WeakRef or calling a finalizer [cleanup callback] in a timely, predictable manner, it's likely to be disappointed: the cleanup may happen much later than expected, or not at all. Sources of variability include:
- 一个对象可能比另一个对象更快地被垃圾收集,即使它们同时变得不可访问,例如由于分代收集。
- 垃圾收集工作可以使用增量和并发技术随着时间的推移进行分解。
- 可以使用各种运行时启发法来平衡内存使用和响应能力。
- JavaScript 引擎可能会保存对看起来无法访问的事物的引用(例如,在闭包或内联缓存中)。
- 不同的 JavaScript 引擎可能会以不同的方式执行这些操作,或者同一引擎可能会在不同版本之间更改其算法。
- 复杂的因素可能会导致对象保持活动状态的时间超出预期,例如与某些 API 一起使用。
关于清理回调的注意事项
¥Notes on cleanup callbacks
- 开发者不应依赖清理回调来获取基本的程序逻辑。清理回调对于减少程序过程中的内存使用可能很有用,但在其他方面不太可能有用。
- 如果你的代码刚刚向注册表注册了一个值,则该目标在当前 JavaScript job 结束之前不会被回收。详情请参见 关于 WeakRef 的注释。
- 符合规范的 JavaScript 实现,即使是进行垃圾收集的实现,也不需要调用清理回调。何时以及是否这样做完全取决于 JavaScript 引擎的实现。当一个注册的对象被回收时,它的任何清理回调可能会被调用,或者稍后调用,或者根本不调用。
- 主要实现很可能会在执行期间的某个时刻调用清理回调,但这些调用可能基本上是在相关对象被回收之后。此外,如果有一个对象在两个注册表中注册,则不能保证这两个回调会紧接着被调用 - 一个可能被调用而另一个从未被调用,或者另一个可能会在很晚之后被调用。
- 还有一些情况,即使通常调用清理回调的实现也不太可能调用它们:
- 当 JavaScript 程序完全关闭时(例如,关闭浏览器中的选项卡)。
- 当 JavaScript 代码无法再访问
FinalizationRegistry
实例本身时。
- 如果
WeakRef
的目标也在FinalizationRegistry
中,则WeakRef
的目标会在调用与注册表关联的任何清理回调的同时或之前被清除;如果你的清理回调在对象的WeakRef
上调用deref
,它将收到undefined
。
构造函数
实例属性
¥Instance properties
这些属性在 FinalizationRegistry.prototype
上定义并由所有 FinalizationRegistry
实例共享。
¥These properties are defined on FinalizationRegistry.prototype
and shared by all FinalizationRegistry
instances.
FinalizationRegistry.prototype.constructor
-
创建实例对象的构造函数。对于
FinalizationRegistry
实例,初始值为FinalizationRegistry
构造函数。 FinalizationRegistry.prototype[Symbol.toStringTag]
-
[Symbol.toStringTag]
属性的初始值为字符串"FinalizationRegistry"
。该属性在Object.prototype.toString()
中使用。
实例方法
¥Instance methods
FinalizationRegistry.prototype.register()
-
向注册表注册一个对象,以便在该对象被垃圾收集时获得清理回调。
FinalizationRegistry.prototype.unregister()
-
从注册表中取消注册对象。
示例
创建新注册表
注册对象以进行清理
回调从未同步调用
¥Callbacks never called synchronously
无论你给垃圾收集器施加多大的压力,清理回调都不会被同步调用。该对象可以同步回收,但回调总是会在当前作业完成后的某个时间被调用:
¥No matter how much pressure you put on the garbage collector, the cleanup callback will never be called synchronously. The object may be reclaimed synchronously, but the callback will always be called sometime after the current job finishes:
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
但是,如果你允许每次分配之间有一点剩余,则可能会更快调用回调:
¥However, if you allow a little break between each allocation, the callback may be called sooner:
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。
¥There's no guarantee that the callback will be called sooner or if it will be called at all, but there's a possibility that the logged message has a counter value smaller than 5000.
规范
Specification |
---|
ECMAScript Language Specification # sec-finalization-registry-objects |
浏览器兼容性
BCD tables only load in the browser