文章目录

JavaScript FinalizationRegistry在垃圾回收时执行清理回调

发布于 2026-04-29 17:23:38 · 浏览 4 次 · 评论 0 条

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. 最佳实践与注意事项

  1. 不要依赖时机:垃圾回收是被动且不可预测的,回调可能在对象失去引用很久之后才执行。切勿将 FinalizationRegistry 用于关键业务逻辑(如事务提交)。
  2. 避免内存泄漏:如果 heldValueunregisterToken 持有对 target 的强引用,那么 target 永远不会被垃圾回收。确保 heldValue 不直接或间接引用 target
  3. 性能考量:过多的注册操作可能会增加垃圾回收器的压力,仅在必要时(如管理 C++ 模块中的内存或文件系统操作)使用。

移除对目标对象的引用:

// 将引用置为 null,使其符合垃圾回收条件
resourceObject = null;

此时,如果触发了垃圾回收(例如在 Node.js 中手动执行 global.gc() 或等待浏览器自动回收),控制台将输出 cleanupCallback 中定义的日志信息。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文