WeakSet 无法确认节点是否被 GC 回收,has() 返回 false 仅表示未 add 过或当前不可达;它无遍历方法,仅用于避免阻止回收;add 仅接受对象(如 Element),需防护非元素节点;其价值在于“不阻碍 GC”,而非主动追踪。

WeakSet 本身不追踪“已被销毁”的节点,它只反映“当前是否还活着且被标记过”——你无法靠它确认节点是否已被 GC 回收,只能靠它避免自己成为回收障碍。
WeakSet.has(el) 返回 false 不代表节点已销毁
这是最常被误解的一点。WeakSet.has(el) 返回 false 只有两种可能:一是 el 根本没被 add 过;二是 el 曾被加入,但**当前已不可达(无任何强引用)**,GC 已将其连同 WeakSet 内部记录一并清理。你无法区分二者,也无法得知“它是什么时候消失的”。
- 不要在逻辑中依赖
has()的false做“节点已销毁”断言,比如:if (!observed.has(node)) { console.log('节点已销毁'); }—— 这个日志可能在节点刚创建、还没被add时就触发 - WeakSet 没有
size、entries()、forEach(),你不能遍历它来“检查哪些节点还在” - 它不是调试工具,而是设计用来“静默配合 GC”的数据结构
WeakSet.add(el) 报错的常见原因
WeakSet 构造函数只接受对象,且 add 方法对参数极其严格:
-
el是null或undefined→ 报错TypeError: Invalid value used in weak set -
el是文本节点(Text)、注释节点(Comment)或 DocumentFragment → 合法,但通常不是你想要的“DOM 节点”目标 -
el是普通对象({})、字符串、数字 → 同样报Invalid value used in weak set - 正确用法仅限于:
Element、Document、Window等宿主对象,其中 DOM 元素最常用
建议加一层防护:if (el && el.nodeType === Node.ELEMENT_NODE) observed.add(el);
为什么 WeakSet 里节点还在,但页面上已看不到?
这说明该节点仍被其他强引用持有,WeakSet 只是“背锅侠”——它没拦着 GC,但别人拦了。
- 检查是否有未解绑的事件监听器:
el.addEventListener('click', handler)→ 必须配对el.removeEventListener('click', handler) - 检查是否被闭包捕获:
function makeHandler(el) { return () => el.classList.add('active'); }→ 只要返回的函数还活着,el就不会被回收 - 检查是否存入了
Map、Array、全局变量或 Vue/React 组件实例属性中 - WeakSet 和 Map 同时存同一个节点?那 Map 就是“锚点”,GC 永远不会动它
真正能验证 WeakSet 是否起效的方式
别信控制台打印,得看内存快照:
- 打开 Chrome DevTools → Memory 面板 → 点击 Take heap snapshot
- 执行操作:动态创建节点 →
observed.add(node)→node.remove()→ 手动点击垃圾箱图标触发 GC - 再拍一次快照 → 在第二张快照中搜索
Detached HTMLDivElement(或对应类型)→ 如果数量为 0 或明显下降,说明 WeakSet 没拖后腿 - 对比
WeakSet实例的 retained size:正常应趋近于 0;若持续增长,说明你在别处悄悄持有了强引用
WeakSet 的价值不在“我能查到它”,而在“我用了它,却没让事情变得更糟”——这个分寸感,恰恰是最容易被忽略的。










