WeakSet不可遍历且无size、clear等属性,因其设计初衷是作为“存在性登记册”而非容器;唯一合法操作是has(),用于瞬时快照式存在校验,以不干扰GC为根本原则。

WeakSet 不可遍历,不是功能被“砍掉”,而是从设计第一天起就被主动封死——它压根不允许你以任何方式“看到全部内容”。这种限制背后是明确的内存安全逻辑,而非技术妥协。
遍历行为天然构成强引用
JavaScript 引擎判断一个对象是否该被回收,核心依据是“是否还有强引用存在”。而遍历操作(比如 for...of、keys()、Array.from())在执行时,必须把每个元素临时加载进执行上下文。这个过程会让引擎认为:“你正在访问它”,从而隐式建立强引用链。
一旦允许遍历,WeakSet 就失去了“弱”的意义——你只是想查一下“哪些还在”,结果却把本该被回收的对象又钉在了内存里。
- 所有主流引擎(V8、SpiderMonkey、JavaScriptCore)都禁止暴露迭代器接口([Symbol.iterator])
- forEach()、values()、entries() 全部不存在,不是遗漏,是规范强制移除
- 哪怕只是写 console.log(weakSet),控制台也只显示 WeakSet { … },不展开内容——防止开发者误以为“能看就能取”
size 属性会破坏弱引用的不可预测性
WeakSet 的成员数量不是“暂时不知道”,而是“本质上无法稳定定义”。因为 GC 可能在任意时刻清理某个对象,而 WeakSet 必须立刻响应这一变化。
如果提供 size,引擎就得在内部维护一个计数器,并在每次 GC 后同步更新它。这不仅引入不必要的同步开销,更关键的是:返回的数字可能在你读到它的下一毫秒就已失效。
- ECMA-262 规范(§23.4.1.1)明确不定义 size 属性
- 没有 clear() 方法,是因为“清空”本身意味着你掌握了集合的完整生命周期——这与 WeakSet 的被动性相悖
- 试图用 weakSet.size 做条件判断,等于在依赖一个随时可能跳变的幻影值
唯一合法的交互方式只有 has()
has() 是 WeakSet 唯一被允许的“读取”操作,因为它不遍历、不计数、不持有、不暴露——只做一次瞬时快照比对:当前这个对象,是否还被 GC 视为可达?
它不承诺“一直存在”,也不暗示“从未存在过”。返回 true 表示“此刻活着”;返回 false 只说明“此刻找不到”,可能是没加过,也可能是刚被回收。
- 适合用在初始化防重(if (!weakSet.has(this)) setup())
- 可用于异步回调前的状态校验(if (weakSet.has(target)) handle())
- 但绝不能用于轮询等待(while(!weakSet.has(obj))),既不可靠,又浪费资源
它不是容器,而是存在性登记册
WeakSet 的真实角色,不是“存东西的地方”,而是“我见过这个对象吗?”的轻量级应答器。它不管理对象,只登记一次弱存在;不维持状态,只反馈瞬时快照;不提供结构,只守住一条边界:不干扰 GC。
这种克制,正是它在框架、库、DOM 交互等易发内存泄漏场景中真正可靠的原因——它从不越界,也从不撒谎。










