WeakSet

WeakSet 是只能存对象的弱引用集合,适合给对象做临时标记而不妨碍垃圾回收。

#type / concept #status / growing #resource / javascript #resource / ecmascript

[!info] related notes

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 的关键区别

特性SetWeakSet
元素类型任意仅对象
弱引用
可遍历
有 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 适合与对象生命周期绑定的临时标记。
创建于 2026/4/7 更新于 2026/5/27