object和map 和 WeakMap 的区别

object 和 map的区别

选择 Object 的场景:

  1. 简单的结构化数据/记录:当你需要创建一个结构固定的"东西",并且属性名是简单、已知的字符串时。例如,一个用户对象 const user = { name: 'John', email: '...' }。
  2. JSON 操作:当数据需要通过 JSON.stringify() 或 JSON.parse() 进行序列化和反序列化时,必须使用 Object,因为 JSON 只支持字符串键。
  3. 表示一个实体:当代码的意图是描述一个具体事物的属性时。

Object 当作一个"记录 (Record)"或"结构体 (Struct)",把 Map 当作一个真正的"字典 (Dictionary)"或"哈希表 (Hash Map)"。

map的key可以是任何类型map对它的key强引用的关系。也就是即使对象类型置空,对象类型依然会被map引用。导致无法被回收。(引用计数器不为零,无法被回收)

WeakMap 是 JavaScript (ES6) 中引入的一种新的集合类型。要理解它的核心作用,关键在于理解两个词:"Map" 和 "Weak"(弱引用)。


1. 首先,它是一个 Map

和普通的 Map 一样,WeakMap 也是一个键值对(key-value)的集合。你可以用 set(key, value) 来添加数据,用 get(key) 来获取数据,用 has(key) 来检查是否存在,用 delete(key) 来删除。

codeJavaScript

javascript 复制代码
const wm = new WeakMap();
const obj = {};

// 设置键值对
wm.set(obj, '一些与obj相关的数据');

// 获取值
console.log(wm.get(obj)); // '一些与obj相关的数据'

// 检查是否存在
console.log(wm.has(obj)); // true

// 删除
wm.delete(obj);
console.log(wm.has(obj)); // false

但是,WeakMap 和 Map 有几个关键的区别,这些区别正是 WeakMap 价值的体现。


2. 核心特性:"Weak"(弱引用)

这是 WeakMap 最重要、最核心的概念。要理解"弱引用",我们先要明白什么是"强引用"。

强引用 (Strong Reference)

在 JavaScript 中,我们平时使用的都是强引用。比如:

codeJavaScript

ini 复制代码
let obj = { name: 'Alice' }; // 变量 obj 对 { name: 'Alice' } 这个对象是一个强引用
let anotherObj = obj;       // 变量 anotherObj 也对这个对象产生了一个强引用

只要至少还有一个强引用指向一个对象,垃圾回收机制(Garbage Collection, GC)就不会回收这个对象。在上面的例子中,即使我们设置 obj = null,anotherObj 仍然引用着那个对象,所以它不会被回收。

弱引用 (Weak Reference)

弱引用则不同。一个弱引用不会阻止垃圾回收机制回收它所引用的对象

WeakMap 对它的**键(key)**就是弱引用

让我们通过一个 Map 和 WeakMap 的对比来理解这一点:

使用 Map (强引用) 的情况:

codeJavaScript

javascript 复制代码
let myMap = new Map();
let keyObject = { id: 1 };

myMap.set(keyObject, "一些元数据");

// 现在,myMap 强引用着 keyObject。

// 我们尝试"忘记"keyObject,把对它的所有其他引用都断开
keyObject = null;

// 此时,keyObject 还能被访问到吗?
// 答案是:可以,通过 myMap。
// 因为 myMap 内部的引用是强引用,所以 { id: 1 } 这个对象无法被垃圾回收。
// 这就造成了潜在的内存泄漏!只要 myMap 存在,这个对象就永远存在。
console.log(myMap.keys().next().value); // { id: 1 }

使用 WeakMap (弱引用) 的情况:

codeJavaScript

csharp 复制代码
let myWeakMap = new WeakMap();
let keyObject = { id: 1 };

myWeakMap.set(keyObject, "一些元数据");

// 现在,myWeakMap 弱引用着 keyObject。

// 我们同样尝试"忘记"keyObject
keyObject = null;

// 此时,再也没有任何强引用指向 { id: 1 } 这个对象了。
// 垃圾回收机制在下一次运行时,就会发现这个对象可以被安全地回收。
// 一旦对象被回收,WeakMap 中对应的这个键值对也会被自动移除。

// 注意:我们无法直接验证这一点,因为垃圾回收的时机是不确定的。
// 但我们可以确信,这个键值对不会永久存在,从而避免了内存泄漏。

总结一下弱引用的效果: WeakMap 允许你将数据与一个对象关联起来,但当这个对象在程序的其他地方不再被需要(即没有任何强引用指向它)时,WeakMap 不会成为它继续存活下去的"救命稻草"。它会自动"放手",让垃圾回收机制清理内存。


3. WeakMap 的主要作用和应用场景

基于"弱引用"这个核心特性,WeakMap 主要被用来解决以下几类问题:

场景一:存储对象的私有数据或元数据(最经典的应用)

当你想要为一个对象附加一些额外信息,但又不想直接修改这个对象本身时,WeakMap 是完美的解决方案。

问题: 假设你正在编写一个库,需要为用户传入的对象添加一些内部状态,但你不能在对象上添加属性(比如 userObject._myLibraryState = ...),因为这会污染对象,可能与用户的代码冲突。

使用 Map 的缺陷: 你可以用一个全局的 Map 来存储:map.set(userObject, myState)。但这会导致前面提到的内存泄漏问题。如果用户用完 userObject 并将其设置为 null,你的 Map 仍然会"抓住"它不放。

WeakMap 的解决方案:

codeJavaScript

javascript 复制代码
const privateData = new WeakMap();

class User {
  constructor(name) {
    this.name = name;
    // 将私有数据存储在 WeakMap 中,用 this (实例对象) 作为键
    privateData.set(this, { loginCount: 0, lastLogin: null });
  }

  login() {
    const data = privateData.get(this);
    data.loginCount++;
    data.lastLogin = new Date();
    console.log(`${this.name} 登录成功,已登录 ${data.loginCount} 次。`);
  }
}

let user1 = new User('Bob');
user1.login(); // Bob 登录成功,已登录 1 次。

// 当 user1 不再被使用时,比如:
user1 = null;
// 在未来的某个时刻,垃圾回收机制会回收原来 user1 指向的对象。
// 同时,privateData 中与该对象关联的数据也会被自动清除,不会造成内存泄漏。

这个模式在 JavaScript 实现私有属性的早期探索中非常流行(在 # 私有字段语法普及之前)。

场景二:缓存计算结果 (Memoization)

当你有一个对对象进行复杂计算的函数时,你可以用 WeakMap 来缓存结果,以避免重复计算。

问题: 有一个函数 process(obj),计算成本很高。如果多次传入同一个 obj,我们希望直接返回缓存的结果。

WeakMap 的解决方案:

codeJavaScript

javascript 复制代码
const cache = new WeakMap();

function process(obj) {
  // 如果缓存中有,直接返回
  if (cache.has(obj)) {
    console.log('从缓存中读取...');
    return cache.get(obj);
  }

  // 否则,进行复杂计算
  console.log('正在进行复杂计算...');
  const result = /* ... 一些非常耗时的操作 ... */ obj.value * 10;

  // 将结果存入缓存
  cache.set(obj, result);
  return result;
}

let myObj = { value: 5 };
console.log(process(myObj)); // 正在进行复杂计算...  50
console.log(process(myObj)); // 从缓存中读取...    50

// 当 myObj 不再需要时
myObj = null;
// 缓存中的 { value: 5 } -> 50 这条记录会自动被清理,释放内存。
// 如果用 Map,这条缓存会一直存在。
场景三:管理 DOM 节点与数据的关联

在前端开发中,我们经常需要为 DOM 元素附加一些状态或数据。

问题: 你为一个按钮添加了一个点击事件监听器,并且需要存储一些与该按钮相关的状态。如果把这些状态存在一个全局的 Map 或对象中,当这个按钮从页面上被移除后,Map 中对它的引用依然存在,导致这个(已经分离的)DOM 节点无法被回收,造成内存泄漏。

WeakMap 的解决方案:

codeJavaScript

javascript 复制代码
const elementData = new WeakMap();

const button = document.getElementById('myButton');

elementData.set(button, { clickCount: 0 });

button.addEventListener('click', () => {
  const data = elementData.get(button);
  data.clickCount++;
  console.log(`按钮被点击了 ${data.clickCount} 次`);
});

// 假设在某个时刻,我们从 DOM 中移除了这个按钮
// button.parentNode.removeChild(button);
// 并且没有其他地方引用这个 button 变量了。
// 那么这个 DOM 节点就会被垃圾回收,elementData 中对应的数据也会被自动清除。

4. WeakMap 的限制

为了实现弱引用和自动垃圾回收,WeakMap 付出了一些代价,导致它有以下限制:

  1. 键必须是对象:WeakMap 的键不能是原始类型值(如 string, number, symbol),因为原始值是不可变的,没有被垃圾回收的概念。
  2. 不可遍历:WeakMap 没有 keys(), values(), entries() 方法,也不支持 forEach 循环,也没有 size 属性。因为键值对可能在任何时候被垃圾回收机制移除,所以它的成员列表是不确定的,遍历它没有意义。

总结

WeakMap 的核心作用是在不影响对象生命周期的前提下,将数据与该对象进行关联

✅ WeakMap 的 key 可以是什么?

👇 这些都是合法的 key:

dart 复制代码
const wm = new WeakMap();

wm.set({}, 'obj');          // 普通对象
wm.set([], 'array');        // 数组
wm.set(() => {}, 'fn');     // 函数
wm.set(new Map(), 'map');   // Map
wm.set(new Set(), 'set');   // Set
wm.set(document.body, 'dom'); // DOM 节点
wm.set(new Number(1), 'wrapped number'); // 包装对象
相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax