Skip to content

FinalizationRegistry

[TOC]

索引

构造方法

  • new FinalizationRegistry()(cleanupCallback)ES2021,允许开发者注册一个回调函数,当对象被垃圾回收时自动执行清理操作

方法

  • registry.register()(target, heldValue, unregisterToken?)ES2021,用于注册需要垃圾回收监视的对象
  • registry.unregister()(unregisterToken)ES2021,用于取消之前通过 register() 方法设置的垃圾回收监听。

FinalizationRegistry

构造方法

new FinalizationRegistry()@

new FinalizationRegistry()(cleanupCallback)ES2021,允许开发者注册一个回调函数,当对象被垃圾回收时自动执行清理操作

  • cleanupCallback(heldValue)=>void,当注册对象被垃圾回收时触发的清理回调函数。

    • heldValueany,注册对象时提供的标识值。
  • 返回:

  • registryFinalizationRegistry,返回一个实例对象。

核心特性

  1. 完整类型定义

    ts
    declare class FinalizationRegistry {
      constructor(cleanupCallback: (heldValue: any) => void);
      
      register(
        target: object,
        heldValue: any,
        unregisterToken?: object
      ): void;
      
      unregister(unregisterToken: object): boolean;
    }
  2. 不可预测性

    • 回调执行时间不确定(可能延迟数分钟)
    • 程序崩溃/退出时回调不会触发
  3. 弱引用原则:强引用会导致对象无法回收

  4. 优先使用显式资源管理

    js
    // 使用 finally 块确保清理
    try {
      const resource = acquireResource();
    } finally {
      releaseResource(); // 确定性的清理
    }

示例

  1. 文件句柄管理

    js
    // 文件句柄管理
    const fileRegistry = new FinalizationRegistry(fileId => {
      console.log(`自动关闭文件: ${fileId}`);
      fs.closeSync(fileId);
    });
    
    function openFile(path) {
      const handle = fs.openSync(path, 'r');
      const token = { path }; // 取消令牌
      
      fileRegistry.register(handle, handle.fd, token);
      return { handle, token };
    }
    
    // 使用文件
    const { handle, token } = openFile('data.txt');
    readFile(handle);
    
    // 显式关闭(优先选择)
    fileRegistry.unregister(token);
    fs.closeSync(handle.fd);
    
    // 若忘记关闭,垃圾回收后会触发自动关闭

方法

register()@

registry.register()(target, heldValue, unregisterToken?)ES2021,用于注册需要垃圾回收监视的对象

  • targetobject,要监视垃圾回收的对象。要求:

    • 必须是对象类型(非原始值)

    • target 的引用是弱引用,不会阻止垃圾回收。

  • heldValueany,回调触发时传递给清理函数的值。最佳实践:

    • 推荐使用字符串/数字/简单对象。

    • 避免直接引用 target 本身(防止内存泄漏)

  • unregisterToken?object|undefined用于后续取消注册的令牌要求:

    • 必须是对象类型(如果提供)。

    • 建议使用专用令牌对象。

核心特性

  1. 覆盖注册

    同一对象多次注册会覆盖之前的注册:

    js
    registry.register(obj, "first");
    registry.register(obj, "second"); // 覆盖第一次注册
    
    // 回收时只会触发 "second" 的回调
  2. 多次注册

    同一对象可在不同注册表独立注册:需要同时注册多个时使用不同注册表。

    js
    const reg1 = new FinalizationRegistry(v => console.log(`Reg1: ${v}`));
    const reg2 = new FinalizationRegistry(v => console.log(`Reg2: ${v}`));
    
    reg1.register(obj, "id1");
    reg2.register(obj, "id2");
    
    // 回收时会触发两个回调
  3. 令牌使用

    • 同一令牌可注册多个对象:

      js
      const token = {};
      
      registry.register(obj1, "res1", token);
      registry.register(obj2, "res2", token);
      
      // 取消注册会移除所有关联对象
      registry.unregister(token);
    • 不同令牌独立管理:

      js
      const tokenA = {};
      const tokenB = {};
      
      registry.register(obj, "A", tokenA);
      registry.register(obj, "B", tokenB); // 覆盖第一次注册
      
      // 取消 tokenA 无效(已被覆盖)
      registry.unregister(tokenA); // 返回 false
      
      // 取消 tokenB 有效
      registry.unregister(tokenB); // 返回 true
  4. 垃圾回收条件

    对象被回收的必要条件:

    • 必须移除所有强引用
    • 可能需要手动触发GC(仅开发环境)
    js
    let obj = {};
    registry.register(obj, "value");
    
    // 必须移除所有强引用
    obj = null; 
    
    // 可能需要手动触发GC(仅开发环境)
    globalThis.gc?.();

unregister()

registry.unregister()(unregisterToken)ES2021,用于取消之前通过 register() 方法设置的垃圾回收监听。

  • unregisterTokenobject,用于标识要取消的注册项。要求:

    • 必须是对象类型(非原始值)

    • 必须与 register() 时使用的令牌严格相等(===

  • 返回:

  • isCanceledboolean,返回是否成功取消了注册。

核心特性

  1. 取消注册关系

    • 移除对象与清理回调的关联
    • 目标对象被回收时不再触发回调
    js
    const token = {};
    const obj = {};
    
    registry.register(obj, "important", token);
    registry.unregister(token); // 取消注册
    
    obj = null; // 即使被回收,也不会触发回调
  2. 令牌的多重注册管理

    • 一个令牌可关联多个对象
    • 取消操作会移除所有关联项
    js
    const token = {};
    const obj1 = {};
    const obj2 = {};
    
    registry.register(obj1, "res1", token);
    registry.register(obj2, "res2", token);
    
    // 取消所有使用此令牌的注册
    registry.unregister(token); // 返回 true
    
    // 两个对象都不再被监视
  3. 令牌引用要求

    必须保留对令牌的引用才能取消

    js
    // 错误:无法保留令牌引用
    registry.register(obj, "value", {});
    
    // 无法取消,因为没有令牌引用
    registry.unregister(/* 无可用令牌 */);

示例

  1. 使用规则

    js
    // 创建令牌
    const token = { id: "unique-token" };
    
    // 注册对象
    registry.register(targetObj, "value", token);
    
    // 正确取消
    registry.unregister(token); // ✅
    
    // 错误示例
    registry.unregister({ id: "unique-token" }); // ❌ 不同对象
    registry.unregister("unique-token");         // ❌ 原始值(TypeError)

应用场景

  1. 网络连接资源管理

    js
    class DatabaseConnection {
      constructor() {
        this.registry = new FinalizationRegistry(connId => {
          console.warn(`未关闭的连接: ${connId}`);
          this.forceClose(connId);
        });
        
        this.connection = createConnection();
        this.token = { connId: this.connection.id };
        
        this.registry.register(
          this.connection, 
          this.connection.id, 
          this.token
        );
      }
      
      close() {
        if (this.connection) {
          // 正常关闭连接
          this.connection.close();
          
          // 取消注册防止重复清理
          const success = this.registry.unregister(this.token);
          
          console.log(success ? "安全取消" : "已自动清理");
          this.connection = null;
        }
      }
    }
    
    // 使用示例
    const db = new DatabaseConnection();
    
    // 正常关闭
    db.close(); // 输出 "安全取消"
    
    // 忘记关闭
    // 垃圾回收后输出 "未关闭的连接: [id]"