Object、Map 、WeakMap的区别

149 阅读4分钟

在面试中被问到了这个问题,自己回答的比较浅显,所以觉得来深究下其终极答案

前置知识

Map() 构造函数

创建Map对象

备注:  Map() 只能用 new 构造。尝试不使用 new 调用它会抛出 TypeError

new Map()
new Map(iterable)
  • 参数(iterable)

一个元素是键值对的数组或其他可迭代 对象。(例如,包含两个元素的数组,如 [[1,'one'],[2, 'two']]。)每个键值对都被添加到新的 Map 中。

const myMap = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

为什么Map对象是可迭代的

  • Map.prototype[@@iterator]

@@iterator 属性的初始值与 entries 属性的初始值是同一个函数对象。

  • Map.prototype.entries()

entries()  方法返回一个新的迭代器对象,其中包含 Map 对象中按插入顺序排列的每个元素的 [key, value] 对。在这种情况下,这个迭代器对象也是一个可迭代对象,因此可以使用 for-of 循环。当使用 [Symbol.iterator] 时,它返回一个函数,该函数在调用时返回迭代器本身。

  • 上述行为可以得出结论
  1. **@@iterator**的值是可以修改的
  2. **@@iterator的默认行为和Map.prototype.entries()**的行为一致

image.png

Map的定义

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值

键的相等

键的比较基于零值相等算法。(它曾经使用同值相等,将 0 和 -0 视为不同。检查浏览器兼容性。)这意味着 NaN 是与 NaN 相等的(虽然 NaN !== NaN),剩下所有其他的值是根据 === 运算符的结果判断是否相等。

image.png

Object 和 Map 的比较

意外的键

  • Map 默认情况不包含任何键。只包含显式插入的键
  • 一个 Object 有一个原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

备注: 虽然可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。

键的类型

  • 一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。
  • 一个 Object 的键必须是一个 String 或是 Symbol

键的顺序

  • Map 中的键是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。
  • 虽然 Object 的键目前是有序的,但并不总是这样,而且这个顺序是复杂的。因此,最好不要依赖属性的顺序。

迭代

  • Map 是 可迭代的 的,所以可以直接被迭代。
  • Object 没有实现 迭代协议,所以使用 JavaSctipt 的 for...of 表达式并不能直接迭代对象。 备注:

性能

  • 在频繁增删键值对的场景下表现更好。
  • 在频繁添加和删除键值对的场景下未作出优化。

代理 Proxy

  • Map无法被代理

image.png

Map的使用场景

  • 只想有个存储键值对的集合
  • 频繁的对键值对进行操作
  • 存储的键的类型不确定

WeakMap

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的

Why WeakMap ?点击访问MDN

map API 可以 通过使其四个 API 方法共用两个数组(一个存放键,一个存放值)来实现。给这种 map 设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。当从该 map 取值的时候,需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。

总结一下

  • Map可能会导致内存泄漏
  • 赋值和搜索浪费性能

Map和WeakMap的区别

相比之下,原生的 WeakMap 持有的是每个键对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。原生 WeakMap 的结构是特殊且有效的,其用于映射的 key _只有_在其没有被回收时才是有效的。

正由于这样的弱引用,WeakMap 的 key 是不可枚举的(没有方法能给出所有的 key)。如果 key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果。因此,如果你想要这种类型对象的 key 值的列表,你应该使用 Map