WeakSet 只能存储对象,不支持原始值,无遍历方法和 size 属性,仅提供 add/has/delete,用于弱引用标记而非持久存储,需配合 FinalizationRegistry 实现回收通知。

WeakSet 只能存对象,原始值会报错
WeakSet 的设计目标就是专门用来弱引用对象,所以它不接受字符串、数字、布尔值等原始类型。传进去会直接抛 TypeError: Invalid value used in weak set。这点和 Set 完全不同——Set 能存一切,WeakSet 只认 typeof x === 'object' && x !== null 的值。
实操建议:
- 存之前先用
Object.isExtensible(obj)或简单判空(obj && typeof obj === 'object')避免运行时崩溃 - 不要试图用
new WeakSet([1, 'a', {}])初始化——数组里混了原始值,整个构造就失败 - 常见误用场景:把 DOM 元素 ID 字符串(如
'#modal-123')当对象传给weakSet.add(),结果卡在错误上
WeakSet 无法遍历,也不能查 size
WeakSet 没有 keys()、values()、entries(),也没有 size 属性。这是刻意为之:一旦提供遍历能力,引擎就无法安全地在任意时刻回收其中的对象(因为遍历本身会隐式持有强引用)。所以你只能做两件事:add()、has()、delete()。
这意味着它不适合做“状态快照”或“调试时打印当前存了几个对象”。如果你需要这类能力,WeakSet 就不是正确工具——该换 Map 或带清理逻辑的 Set。
实操建议:
- 用
weakSet.has(obj)做存在性判断是安全的,且不会阻止回收 - 别写
console.log(weakSet.size)—— 它根本不存在,会得到undefined - 想监控对象生命周期?得配合
FinalizationRegistry,WeakSet 自己不发通知
垃圾回收时机不可控,但引用确实弱
WeakSet 中的对象只要在其他地方不再被强引用,下一次 GC 运行时就可能被清除。但具体哪次 GC、是否立刻清、清几个——完全由 JS 引擎决定,开发者无法预测或触发。你唯一能确定的是:WeakSet 不会阻止回收。
典型验证方式是手动解除所有强引用后强制触发 GC(仅限 Chrome DevTools):gc()(需开启 --js-flags="--expose-gc"),再用 weakSet.has(obj) 检查返回 false。但这只是调试手段,生产环境绝不能依赖。
实操建议:
- 把 WeakSet 当作“临时标记位”:比如防止重复处理某个对象,而不是“持久记录”
- 不要在
weakSet.has(obj)返回false后假设对象已被销毁——它可能还在内存里,只是 WeakSet 不再认它 - DOM 场景最稳:把事件处理器绑定前先
weakSet.add(el),解绑后也不用删,自然随元素消失而失效
WeakSet + FinalizationRegistry 才算完整生命周期管理
WeakSet 单独用只能“被动等待消失”,要真正响应对象被回收,必须配对 FinalizationRegistry。WeakSet 存引用,注册表监听回收,并执行清理回调(比如释放 canvas 上下文、取消定时器)。
注意:注册表的回调是异步的、不保证顺序、甚至可能不执行(如页面卸载时)。所以关键资源释放仍要主动做,注册表只补漏。
实操建议:
- 注册时传入一个“持有者”值(比如字符串标签),回调里能区分是哪个对象被收走了
- 回调中禁止再保留对已回收对象的引用,否则可能造成内存泄漏
- 示例关键链路:
const registry = new FinalizationRegistry(cleanUp); registry.register(obj, 'canvas-handler', obj);—— 第三个参数是“holdings”,会在回调中作为第二个参数传入
WeakSet 的价值不在“存”,而在“不拦着删”。真正难的是判断哪里该用它——不是所有对象缓存都适合弱引用,也不是所有临时标记都需要靠 GC 收尾。多数时候,你得先问自己:这个对象的生命周期,是不是天然跟某个 DOM 节点或外部作用域强绑定?如果不是,WeakSet 很可能帮不上忙。










