WeakSet
WeakSet 是只能存对象的弱引用集合,适合给对象做临时标记而不妨碍垃圾回收。
#type / concept
#status / growing
#resource / javascript
#resource / ecmascript
[!info] related notes
- 所属 MOC: ES6 新特性 MOC, ECMAScript MOC
- 前置概念: ECMAScript集合引用类型
- 并列概念: Set
- 关系笔记: Set vs WeakSet
WeakSet
一句话定义
WeakSet 是只能存对象的弱引用集合,元素被垃圾回收后自动从集合中移除,适合做临时标记而不干扰对象的生命周期。
核心机制
基本用法
const ws = new WeakSet()
const obj1 = { id: 1 }
const obj2 = { id: 2 }
ws.add(obj1)
ws.add(obj2)
ws.has(obj1) // true
ws.delete(obj1) // true
ws.has(obj1) // false
关键特性
- 只能存对象(不能存原始值)
- 弱引用:不阻止垃圾回收
- 不可遍历:没有
keys()、values()、entries()、forEach()、size - 没有
clear()方法 - 同一个对象只能出现一次(集合语义)
弱引用的含义
let node = document.createElement('div')
const visited = new WeakSet()
visited.add(node)
console.log(visited.has(node)) // true
node = null // div 元素失去唯一引用
// GC 回收后,WeakSet 中对应的条目也自动消失
实际用途
1. 防止重复处理
const processed = new WeakSet()
function process(obj) {
if (processed.has(obj)) return // 已处理过,跳过
processed.add(obj)
// 处理逻辑...
}
对象被回收后,标记自动消失,不会造成内存泄漏。
2. 递归引用检测(防止循环引用)
function stringify(obj, seen = new WeakSet()) {
if (typeof obj === 'object' && obj !== null) {
if (seen.has(obj)) {
return '[Circular]'
}
seen.add(obj)
const result = Array.isArray(obj)
? `[${obj.map(item => stringify(item, seen)).join(',')}]`
: `{${Object.entries(obj).map(([k, v]) => `"${k}":${stringify(v, seen)}`).join(',')}}`
seen.delete(obj) // 回溯时移除,允许不同路径引用同一对象
return result
}
return JSON.stringify(obj)
}
3. 标记正在处理中的对象(状态锁)
const locking = new WeakSet()
async function safeProcess(obj) {
if (locking.has(obj)) return // 正在处理中
locking.add(obj)
try {
await doHeavyWork(obj)
} finally {
locking.delete(obj)
}
}
4. DOM 节点标记
const animated = new WeakSet()
function animate(node) {
if (animated.has(node)) return
animated.add(node)
node.classList.add('fade-in')
node.addEventListener('animationend', () => {
node.classList.remove('fade-in')
animated.delete(node)
}, { once: true })
}
DOM 节点被移除后,标记自动清理。
与 Set 的关键区别
| 特性 | Set | WeakSet |
|---|---|---|
| 元素类型 | 任意 | 仅对象 |
| 弱引用 | 否 | 是 |
| 可遍历 | 是 | 否 |
| 有 size | 是 | 否 |
| GC 行为 | 阻止回收 | 不阻止回收 |
边界与常见误解
- 不能存基本类型(number、string 等),尝试
ws.add(1)会抛 TypeError。 - 不可遍历是有意设计:因为元素随时可能被 GC 清理,遍历结果不可靠。
- WeakSet 的
has/add/delete时间复杂度接近 O(1)。 ws.delete(obj)返回布尔值表示是否删除成功,不是被删除的元素。- WeakSet 适合”标记”场景,WeakMap 适合”附加数据”场景。如果需要存储关联信息,用 WeakMap。
- WeakSet 不是 Set 的”弱化版”,它们解决不同问题。Set 适合需要遍历和管理元素的场景,WeakSet 适合与对象生命周期绑定的临时标记。