彻底搞明白Map和WeakMap

964 阅读5分钟

在 JavaScript 中,MapWeakMap 都是用于存储键值对的集合数据结构。它们在许多方面有相似之处,但也有关键的区别,特别是在如何处理键和垃圾回收方面。下面将详细介绍它们的特点、用法及其区别。

1. Map:常规的键值对集合

Map 是一种有序的键值对集合,允许任何类型的键,并且会按照插入顺序进行迭代。Map 在 ES6 中引入,它是用来替代普通对象(Object)的,与普通的 JavaScript 对象不同,Map 的键可以是任何数据类型,用于需要更复杂操作的数据存储场景。

1.1 Map 的基本特点

  • 键的类型:可以是任何类型(包括对象、函数、基本类型(如字符串、数字等))。
  • 插入顺序Map 会按插入顺序保存键值对,保证迭代顺序。
  • 性能:对于频繁插入和删除的操作,Map 比普通对象更高效,因为它的插入、查找、删除操作时间复杂度通常为 O(1)。
  • 没有原型链问题Map 是一个纯粹的数据结构,没有继承自 Object.prototype,因此不存在继承链上的属性(如 toStringhasOwnProperty 等)干扰。
  • 大小Map 有一个 size 属性,可以返回 Map 中键值对的数量。
  • 迭代Map 本身是可迭代的,支持 forEach 循环和其他迭代方法(如 for...of)。

1.2 Map 的常用方法

  • set(key, value):添加或更新一个键值对。
  let map = new Map();
  map.set('name', 'Alice');
  map.set(1, 'one');
  • get(key):根据键获取值。
  console.log(map.get('name'));  // 'Alice'
  console.log(map.get(1));       // 'one'
  • has(key):检查是否存在某个键。
  console.log(map.has('name'));  // true
  console.log(map.has('age'));   // false
  • delete(key):删除指定键的键值对。
  map.delete('name');  
  console.log(map.has('name'));  // false
  • clear():清空所有键值对。
  map.clear();
  console.log(map.size);  // 0
  • size:返回 Map 中键值对的数量。
  console.log(map.size);  // 2
  • forEach():对每个键值对执行回调函数。
  map.forEach((value, key) => {
    console.log(key, value);
  });
  • keys() values() entries():分别返回键、值和键值对的迭代器。
  for (let key of map.keys()) {
    console.log(key);
  }


  for (let value of map.values()) {
    console.log(value);
  }


  for (let [key, value] of map.entries()) {
    console.log(key, value);
  }

1.3 Map 的优势

  • 更灵活的键类型:与普通对象只能使用字符串作为键不同,Map 可以使用任意类型(包括对象和函数)作为键。
  • 保持插入顺序Map 会根据插入顺序返回键值对,这是普通对象无法保证的。
  • 避免原型链干扰Map 不会受到继承链中其他属性的影响(如 hasOwnPropertytoString 等)。
  • 高效的性能Map 在频繁的插入、删除和查找操作中通常更高效。

 

2. WeakMap:带有垃圾回收机制的键值对集合

WeakMapMap 的一种变体,专门用于存储对象的引用,并且具有“弱引用”机制。WeakMap 中的键必须是对象,而其值可以是任意类型,WeakMap 不会阻止其键对象被垃圾回收。

2.1 WeakMap 的基本特点

  • 键必须是对象WeakMap 只能使用对象作为键,不能使用原始值(如数字、字符串等)。这是因为 WeakMap 会使用“弱引用”来存储键,而弱引用是指当键不再有其他引用时,它可以被垃圾回收机制自动清除。
  • 垃圾回收WeakMap 的键是弱引用的,这意味着如果 WeakMap 中的某个键没有其他引用时,它会被垃圾回收机制自动清除,不会阻止该对象被回收。与此相对,普通的 Map 会保持对键的强引用,直到手动删除。
  • 不支持迭代WeakMap 不支持像 Map 那样进行迭代(没有 forEach()keys()values()entries() 方法),因为 WeakMap 的键值对会在没有其他引用时被自动清除,无法保证稳定的迭代顺序。
  • 内存管理WeakMap 适用于需要关联一些对象的元数据,并且希望在这些对象不再被使用时自动释放内存的场景。

2.2 WeakMap 的常用方法

  • set(key, value):添加一个键值对,其中 key 必须是一个对象。
  let wm = new WeakMap();
  let obj = {};
  wm.set(obj, 'value');
  • get(key):获取指定键对应的值。
  console.log(wm.get(obj));  // 'value'
  • has(key):检查 WeakMap 中是否存在指定的键。
  console.log(wm.has(obj));  // true
  • delete(key):删除指定的键值对。
  wm.delete(obj);
  console.log(wm.has(obj));  // false

2.3 WeakMap 的使用场景

  • 元数据存储WeakMap 常用于为对象存储元数据(例如,缓存、附加信息等)。由于其键是弱引用,WeakMap 不会阻止键所引用的对象被垃圾回收,从而避免内存泄漏。
  • 避免内存泄漏:在浏览器开发中,WeakMap 可以避免某些对象被不必要地保留在内存中,特别是当对象的生命周期与 WeakMap 中的存储数据关联时。
  • 隐式对象属性WeakMap 用于为对象提供隐式属性(如附加数据),这些属性不会暴露给外部代码,也不容易被遍历或修改。

 

3. Map 和 WeakMap 的主要区别

特性MapWeakMap
键的类型任何类型的值(包括原始值和对象)只能是对象,不能是原始值(如字符串、数字等)
垃圾回收键值对不会被垃圾回收,除非显式删除键是弱引用,当没有其他引用时,垃圾回收会自动删除键值对
内存管理不会自动释放内存,需要手动清除自动释放内存,不会阻止垃圾回收
迭代支持迭代(forEach()、keys()、values()、entries())不支持迭代,无法获取键值对
常用场景适用于需要频繁增删改查的场景适用于关联对象的元数据、避免内存泄漏的场景

总结

  • Map:适合需要频繁插入、删除和访问的场景,支持任意类型的键,支持迭代,且键值对在内存中常驻,直到被显式删除。
  • WeakMap:适合需要存储对象的场景,尤其是在需要避免对象因被 Map 引用而无法垃圾回收的情况。由于键是弱引用,WeakMap 有助于内存管理,但不支持迭代,也没有 size 属性。