WeakMap

WeakMap 是只能以对象为键、并且不阻止键被垃圾回收的键值对集合。

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

[!info] related notes

WeakMap

一句话定义

WeakMap 是一种弱引用键值对集合,键只能是对象;当键对象没有其他引用时,对应的键值对会被垃圾回收,适合给对象附加不干扰生命周期的元数据。

核心机制

基本用法

const wm = new WeakMap()
const obj = { name: 'foo' }

wm.set(obj, { visits: 0 })
wm.get(obj)     // { visits: 0 }
wm.has(obj)     // true
wm.delete(obj)  // true

关键特性

  • 键只能是对象(不能是原始值)
  • 键是弱引用:不影响垃圾回收
  • 不能遍历:没有 keys()values()entries()forEach()size
  • 没有 clear() 方法

弱引用的含义

let user = { name: 'Alice' }
const metadata = new WeakMap()

metadata.set(user, { role: 'admin' })
console.log(metadata.get(user))  // { role: 'admin' }

user = null  // user 对象失去唯一引用
// 之后某个时刻,GC 回收 { name: 'Alice' } 对象
// WeakMap 中对应的键值对也会被自动清除

弱引用意味着:如果键对象在其他地方不再被引用,GC 可以回收它,WeakMap 不会阻止这个过程。你无法手动触发 GC,所以 WeakMap 的条目何时消失是不确定的。

实际用途

1. 给对象附加私有数据

const _private = new WeakMap()

class User {
  constructor(name, password) {
    _private.set(this, { name, password })
  }

  getName() {
    return _private.get(this).name
  }

  checkPassword(pwd) {
    return _private.get(this).password === pwd
  }
}

const user = new User('Alice', 'secret')
user.getName()        // "Alice"
// 外部无法直接访问 password

这种方式比闭包更灵活,每个实例的数据独立存储,实例被回收后数据也自动清理。

2. 缓存计算结果

const cache = new WeakMap()

function expensiveProcess(obj) {
  if (cache.has(obj)) {
    return cache.get(obj)
  }
  const result = /* 复杂计算 */ obj.data.length * 2
  cache.set(obj, result)
  return result
}

对象不再使用时,缓存自动清理,不会造成内存泄漏。适合对 DOM 节点、大型数据对象做缓存。

3. 存储 DOM 节点关联数据

const nodeData = new WeakMap()

function bindTooltip(node, text) {
  nodeData.set(node, { text, visible: false })
  node.addEventListener('mouseenter', () => {
    const data = nodeData.get(node)
    data.visible = true
    showTooltip(node, data.text)
  })
  node.addEventListener('mouseleave', () => {
    nodeData.get(node).visible = false
    hideTooltip(node)
  })
}

// 当 DOM 节点被移除时,关联数据自动清理

4. 标记已处理对象(配合 WeakSet)

const visited = new WeakMap()

function deepClone(obj, seen = new WeakMap()) {
  if (seen.has(obj)) return seen.get(obj)
  const copy = Array.isArray(obj) ? [] : {}
  seen.set(obj, copy)
  for (const key of Object.keys(obj)) {
    copy[key] = typeof obj[key] === 'object' ? deepClone(obj[key], seen) : obj[key]
  }
  return copy
}

与 Map 的关键区别

特性MapWeakMap
键类型任意仅对象
弱引用
可遍历
有 size
GC 行为阻止回收不阻止回收

边界与常见误解

  • WeakMap 不是更高级的 Map,而是专用工具。它适合”给对象附加数据”的场景,不适合做通用字典。
  • 弱引用不等于”自动删除”。条目何时被清理取决于 GC 的实现,你不能依赖某个时间点去检查条目是否存在。
  • 不能遍历 WeakMap 是有意为之:因为条目随时可能被 GC 清理,遍历结果不可靠。
  • 原始值(number、string、boolean、null、undefined、symbol)不能作为 WeakMap 的键。
  • WeakMap 的 get / set / has / delete 时间复杂度接近 O(1)。
  • Vue 3 的响应式系统用 WeakMap 存储原始对象到代理对象的映射(targetMap),正是利用了 WeakMap 不阻止 GC 的特性。
创建于 2026/4/7 更新于 2026/5/27