JavaScript FinalizationRegistry在垃圾回收时执行清理回调
JavaScript 的垃圾回收机制通常会自动管理内存,但在某些涉及外部资源(如文件句柄、数据库连接或 WASM 内存)的场景下,仅靠垃圾回收是不够的。FinalizationRegistry 提供了一种在垃圾回收器回收对象时执行清理回调的方法。以下是具体的使用步骤和原理说明。
1. 创建注册表实例
定义一个回调函数,用于在对象被垃圾回收时执行清理逻辑。该回调函数接收一个 heldValue(注册时持有的值),而不是被回收的对象本身。
实例化 FinalizationRegistry,并将上述回调函数作为参数传入。
// 创建清理回调函数
const cleanupCallback = (heldValue) => {
console.log(`清理资源,持有值为: ${heldValue}`);
};
// 实例化注册表
const registry = new FinalizationRegistry(cleanupCallback);
```
---
### 2. 注册目标对象
**获取**你需要追踪的对象(目标对象)。**准备**一个与该对象关联的“持有值”,这个值可以是任意类型(如字符串、对象),将在回调函数中原样传递,用于标识资源或传递清理所需的数据。
**调用** `registry.register()` 方法,将目标对象、持有值以及可选的注销令牌传入。
```javascript
// 示例:模拟一个包含外部资源的对象
const resourceObject = { data: "重要的业务数据" };
// 注册对象
// 参数1: 目标对象 (target)
// 参数2: 持有值 (heldValue) - 回调时会用到
// 参数3: 注销令牌 (unregisterToken) - 可选,用于后续手动注销
registry.register(resourceObject, "资源ID-12345", resourceObject);
```
**注意**:注册操作不会阻止垃圾回收,它只是建立了一种“当对象消失时通知我”的机制。
---
### 3. 理解生命周期与注销
一旦目标对象的引用被断开,且垃圾回收器判定该对象可回收,注册表中的回调函数将在未来的某个时刻被异步调度。由于垃圾回收的时机不可预测,回调执行的时间也是不确定的。
如果你在垃圾回收发生前已经手动清理了资源,**必须调用** `registry.unregister()` 方法并传入注册时使用的令牌,以防止回调函数执行造成重复清理。
```javascript
// 模拟手动清理资源
function manualCleanup() {
console.log("手动执行清理");
// 传入注册时的第三个参数(注销令牌)
registry.unregister(resourceObject);
}
// 执行手动清理
manualCleanup();
```
为了更直观地理解对象从注册到被清理(或手动注销)的流转过程,请参考以下流程图:
```mermaid
graph LR
A[Start: Create Object] --> B[Action: register target]
B --> C[State: Object in Use]
C --> D{Event: Reference Lost?}
D -- No --> C
D -- Yes --> E{GC Run?}
E -- No --> D
E -- Yes --> F[Event: Execute Callback]
C --> G{Action: Manual Cleanup?}
G -- Yes --> H[Action: unregister token]
H --> I[End: Stop Tracking]
F --> I
```
---
### 4. 处理复杂数据结构
当需要管理的资源涉及多个关联对象时,可以利用 `heldValue` 传递包含上下文信息的对象,以便在回调中执行复杂的清理逻辑。
**构造**一个包含多个资源 ID 或配置的对象作为 `heldValue`。
```javascript
const complexResource = { name: "ComplexData" };
// 定义复杂的持有值
const cleanupInfo = {
fileId: "file_001",
dbConnection: "conn_abc",
timestamp: Date.now()
};
registry.register(complexResource, cleanupInfo);
// 回调函数逻辑更新
const advancedCleanup = (info) => {
console.log(`释放文件: ${info.fileId}`);
console.log(`关闭连接: ${info.dbConnection}`);
};
const advancedRegistry = new FinalizationRegistry(advancedCleanup);
advancedRegistry.register(complexResource, cleanupInfo);
5. 关键参数说明
下表详细列出了 register 方法与回调函数中涉及的关键参数及其用途。
| 参数名 | 所属位置 | 类型 | 描述 |
|---|---|---|---|
target |
register |
Object | 必须是对象,垃圾回收器将监视此对象。 |
heldValue |
register |
Any | 回调函数接收到的值,用于传递清理所需的上下文。 |
unregisterToken |
register |
Any | 可选。用于后续调用 unregister 以取消注册的标识符。 |
heldValue |
callback |
Any | 由 register 传入的持有值,注意:此时 target 对象已被回收,不可访问。 |
6. 最佳实践与注意事项
- 不要依赖时机:垃圾回收是被动且不可预测的,回调可能在对象失去引用很久之后才执行。切勿将
FinalizationRegistry用于关键业务逻辑(如事务提交)。 - 避免内存泄漏:如果
heldValue或unregisterToken持有对target的强引用,那么target永远不会被垃圾回收。确保heldValue不直接或间接引用target。 - 性能考量:过多的注册操作可能会增加垃圾回收器的压力,仅在必要时(如管理 C++ 模块中的内存或文件系统操作)使用。
移除对目标对象的引用:
// 将引用置为 null,使其符合垃圾回收条件
resourceObject = null;
此时,如果触发了垃圾回收(例如在 Node.js 中手动执行 global.gc() 或等待浏览器自动回收),控制台将输出 cleanupCallback 中定义的日志信息。

暂无评论,快来抢沙发吧!