JavaScript WeakMap 与 WeakSet 深度解析

0 阅读1分钟

一、Map/Set 的内存问题

普通的 Map 和 Set 会阻止垃圾回收。

let user = { name: 'Alice' };
const map = new Map();
map.set(user, 'metadata');

user = null;  // 想释放 user
// 但 map 仍然持有引用,user 不会被回收

WeakMap 和 WeakSet 使用弱引用,不会阻止垃圾回收。


二、WeakMap 基础

特点

  • 键必须是对象
  • 键是弱引用
  • 不可遍历
  • 没有 size 属性
const wm = new WeakMap();

let obj = { name: 'Alice' };
wm.set(obj, 'metadata');

console.log(wm.get(obj));  // 'metadata'

obj = null;  // obj 可以被垃圾回收
// wm 中的条目也会自动删除

API

const wm = new WeakMap();

wm.set(key, value);  // 设置
wm.get(key);  // 获取
wm.has(key);  // 检查
wm.delete(key);  // 删除

三、WeakMap 实战应用

应用 1:私有属性

const privateData = new WeakMap();

class Person {
  constructor(name, age) {
    privateData.set(this, { name, age });
  }
  
  getName() {
    return privateData.get(this).name;
  }
  
  getAge() {
    return privateData.get(this).age;
  }
}

const person = new Person('Alice', 25);
console.log(person.getName());  // 'Alice'
console.log(person.name);  // undefined(无法直接访问)

应用 2:DOM 节点缓存

const cache = new WeakMap();

function processElement(element) {
  if (cache.has(element)) {
    return cache.get(element);
  }
  
  const result = expensiveOperation(element);
  cache.set(element, result);
  return result;
}

// 当 DOM 节点被移除时,缓存自动清理

应用 3:对象元数据

const metadata = new WeakMap();

function addMetadata(obj, data) {
  metadata.set(obj, data);
}

function getMetadata(obj) {
  return metadata.get(obj);
}

const user = { name: 'Alice' };
addMetadata(user, { createdAt: Date.now(), role: 'admin' });

四、WeakSet 基础

特点

  • 值必须是对象
  • 值是弱引用
  • 不可遍历
  • 没有 size 属性
const ws = new WeakSet();

let obj = { name: 'Alice' };
ws.add(obj);

console.log(ws.has(obj));  // true

obj = null;  // obj 可以被垃圾回收

API

const ws = new WeakSet();

ws.add(value);  // 添加
ws.has(value);  // 检查
ws.delete(value);  // 删除

五、WeakSet 实战应用

应用 1:标记对象

const disabledElements = new WeakSet();

function disableElement(element) {
  disabledElements.add(element);
  element.setAttribute('disabled', true);
}

function isDisabled(element) {
  return disabledElements.has(element);
}

应用 2:防止循环引用

function traverse(obj, visited = new WeakSet()) {
  if (visited.has(obj)) {
    return;  // 已访问过,避免死循环
  }
  
  visited.add(obj);
  
  for (const key in obj) {
    if (typeof obj[key] === 'object') {
      traverse(obj[key], visited);
    }
  }
}

六、与 Map/Set 对比

特性Map/SetWeakMap/WeakSet
键/值类型任意只能是对象
垃圾回收阻止不阻止
可遍历
size
使用场景通用集合内存敏感场景

七、实用技巧

技巧 1:实现观察者模式

const observers = new WeakMap();

function observe(target, callback) {
  if (!observers.has(target)) {
    observers.set(target, new Set());
  }
  observers.get(target).add(callback);
}

function notify(target, data) {
  const callbacks = observers.get(target);
  if (callbacks) {
    callbacks.forEach(cb => cb(data));
  }
}

技巧 2:记忆化函数

const memoCache = new WeakMap();

function memoize(fn) {
  return function(obj) {
    if (memoCache.has(obj)) {
      return memoCache.get(obj);
    }
    const result = fn(obj);
    memoCache.set(obj, result);
    return result;
  };
}