先记住这个答案
现代 WeakMap 的键可以是对象或非注册 Symbol,不能简单说只能用对象;Symbol.for 创建的注册 Symbol,以及字符串、数字等不能作为键。它不会仅因保存键就让键保持强可达,适合给对象附加只在对象存活期间有意义的元数据。键按身份查找,没有 keys、entries 或 size 等枚举能力,也不能据此观察垃圾回收时机。如果其他数组、闭包或全局变量仍强引用对象,它仍可能存活。WeakMap 不替代显式取消订阅、关闭连接或有容量上限的缓存策略。
- 现代键范围包括对象和非注册 Symbol
- 弱键不会单独阻止回收,但其他强引用仍有效
- 不可枚举,也不承诺确定回收时间
对象身份与键类型需要分别验证
示例先给一个任务对象写入元数据,再用结构相同的新对象查询,结果不会命中。随后使用非注册 Symbol 和注册 Symbol 对照现代键规则,错误名称比依赖某一引擎的具体错误文案更稳定。
这些测试只验证可观察的键操作,不尝试断言某个对象几毫秒后被回收。回收受引擎和运行状态影响,用定时器等待后期待条目消失不是可靠的功能测试。
const metadata = new WeakMap();
const task = { id: 1 };
metadata.set(task, { checked: true });
console.log(metadata.get(task).checked);
console.log(metadata.has({ id: 1 }));
const token = Symbol('local');
metadata.set(token, 'allowed');
console.log(metadata.get(token));
try { metadata.set(Symbol.for('shared'), 'invalid'); }
catch (error) { console.log(error.name); }查看输出与解释
true
false
allowed
TypeError对象查找按身份,内容相同的新对象不会命中。非注册 Symbol 可以作为现代弱键,注册 Symbol 被拒绝;示例的确定输出不包含任何垃圾回收时机判断。
缓存适用性取决于是否只需按已知对象查询
例如为语法树节点缓存一次分析结果,调用方已经持有节点,只需要按节点取元数据,不需要列出所有历史节点,WeakMap 就能避免缓存表本身额外延长节点生命。
如果产品需要展示全部缓存条目、按最近使用淘汰或限制最大数量,WeakMap 的不可枚举会成为限制。应选择有明确容量与删除策略的 Map 等结构,不能因为名字带 Weak 就认为它自动实现了理想缓存。
弱关联不等于外部资源会自动释放
元数据如果包含关闭函数、订阅或网络资源,垃圾回收不会自动按业务顺序调用它们。组件卸载或任务结束时仍应显式取消,WeakMap 只改变对象可达关系,不能承担确定性的资源协议。
还要检查值是否被其他地方强引用,并经由它再次引用键。WeakMap 内部值回指键并不单独让这对关联永久存活,但外部强引用链可能改变结果,分析必须从实际根引用出发。
容易答错的地方
- 继续背 WeakMap 只能放对象键
- 现代规范还允许非注册 Symbol,旧结论忽略了能力演进。回答应区分键类型规则与目标环境支持,并明确注册 Symbol 不在允许范围,避免把所有 Symbol 混为一类。
- 把对象设为 null 后立即证明内存已释放
- 这只移除一个变量的引用,其他持有者可能仍存在,回收时刻也不确定。应通过引用关系与合适内存诊断定位问题,而不是用一次赋值和短等待断言垃圾回收结果。
面试官还会怎么问?
WeakMap 为什么没有 size?
如果能精确枚举或计数存活键,就会把垃圾回收状态变成可直接观察的业务结果。WeakMap 主要支持持有已知键时的查询;需要完整列表应选择显式管理的强集合。
value 可以是字符串或数字吗?
可以,限制主要针对键,值可以是任意 JavaScript 值。是否适合存放大对象仍取决于键生命周期和其他强引用,不能因为键弱引用就忽略值的体积与共享关系。
键对象自身字段改变会影响查询吗?
不会自动改变身份,仍用同一个引用就能查询元数据。若业务希望按字段内容合并不同对象,需要另外设计稳定键和更新规则,WeakMap 不是结构相等字典。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。