WeakSet 更合适,因其对 DOM 节点持弱引用,不阻止 GC 回收,无需手动清理,避免内存泄漏;而 Set 保持强引用,导致已移除节点滞留、误判。

用 WeakSet 标记已处理的 DOM 节点,核心在于“只标记、不拖累生命周期”——它不会阻止节点被回收,也不需要你手动清理,特别适合动态渲染频繁增删节点的场景。
为什么 WeakSet 比 Set 更合适
普通 Set 会对节点保持强引用。即使节点已从 DOM 中移除、也无其他变量引用,它仍滞留在 Set 里,造成内存堆积和后续逻辑误判(比如 set.has(node) 一直返回 true)。WeakSet 则不同:
- 只接受对象(包括 DOM 节点),不支持字符串或数字等原始值
- 对节点是弱引用:只要节点在别处没了强引用,GC 下次运行时就会把它连同 WeakSet 中的记录一并清理
-
has()返回true仅表示“当前还活着且被标记过”,不保证下一刻仍成立——这反而是安全信号,提醒你别依赖长期稳定状态
典型使用模式:初始化防重逻辑
常见于组件库、插件系统或自定义指令中,比如为每个目标节点绑定一次 ResizeObserver 或初始化富文本编辑器:
- 声明一个模块级 WeakSet:
const initialized = new WeakSet(); - 在渲染/挂载逻辑中检查:
if (!initialized.has(node)) { initNode(node); initialized.add(node); } - 无需在卸载时调用
delete——节点被移除后,WeakSet 自动“失忆”
注意边界情况
WeakSet 不会帮你解决所有引用问题。如果节点仍被以下任一方式强持有,它就无法被回收,WeakSet 的条目也就不会消失:
- 事件监听器未解绑(如
node.addEventListener('click', handler)) - 闭包中持续引用该节点(比如回调函数里用了
node) - 被全局变量、缓存 Map、数组或其他 Set 显式保存
WeakSet 只是“不添乱”,不是“兜底方案”。真正要释放节点,得先解除所有强引用链。
不适合的场景
别用 WeakSet 做以下事情:
- 需要遍历所有已标记节点(它不支持
forEach、values()或size) - 想长期缓存节点状态(例如“这个按钮上次点击时间”)——该用 WeakMap 存键值对,或干脆用带清理机制的 Map
- 处理你自己创建并完全控制生命周期的对象(比如配置项、内部模型)——这时用普通 Set 更清晰、可测、可控

















