ES6 之 Map 和 WeakMap

724 阅读4分钟

在日常业务开发中,发现有用到 WeakMap 对象,然后对其不是太了解,于是就有了这篇文章。本篇文章主要介绍 Map 和 WeakMap 对象的概念、不同以及 WeakMap 对象中的“weak”如何理解。

Map

Map 对象保存键值对,并且能够记住键的原始插入顺序。其中键可以为任何值(对象或原始值)。

Object 和 Map 的比较

键的类型

Map 的键可以是任何值,包括函数、对象或任何基本类型。而 Object 的键必须是一个 StringSymbol ,如果向其添加了 ObjectArray 类型,最终也会被转换为 String

let obj = new Object();
let map = new Map();

let objKey = {};
let arrKey = [];
let strKey = "string";

obj[objKey] = "这是一个对象";
obj[arrKey] = "这是一个数组";
obj[strKey] = "这是一个字符串";
console.log("obj: ", obj);

map.set(objKey, "这是一个对象");
map.set(arrKey, "这是一个数组");
map.set(strKey, "这是一个字符串");
console.log("map: ", map);

输出如下:

方法

对于 Object 来说,通常使用 obj[number]obj.number 来设置和获取值。但对于 Map 是使用 get()set()has() 等方法来实现相应功能。关于这些方法的使用和更多方法请参考 MDN文档

Size

Map 的键值对个数可以方便地通过 size 属性获取,而 Object 的键值对个数只能手动计算。

迭代

迭代 Object 需要以某种方式先获取键后才能迭代,而 Map 是可迭代对象,可以简单地直接执行迭代。

let map = new Map();
map.set(0, "zero");
map.set(1, "one");
for (let [key, value] of map) {
    console.log(key + " = " + value);
}

let obj = new Object();
obj[0] = "zero";
obj[1] = "one";
for (let key in obj) {
    console.log(key + " = " + obj[key]);
}

WeakMap

WeakMap 对象也是键值对的集合,它的 键必须是对象类型,值可以是任何类型。WeakMap 对象提供的接口和 Map 对象相同,但与 Map 对象最大的不同是 WeakMap 对象是 “weak” 的。

weak 的含义

WeakMap 对象持有的是每个键对象的“弱引用”,这意味着没有其他引用存在时可以进行垃圾回收

垃圾回收

垃圾回收(Garbage Collection,简称 GC)通俗来说就是收集和释放已经分配给对象的内存,而这些内存当前在程序的任何部分都没有被使用。

在像 C 语言这样的编程语言中,我们必须使用 malloc()dealloc() 函数来处理内存的分配和释放。幸运的是, JavaScript 中的垃圾回收是自动执行的,不用我们开发者关心

示例

下面通过 Map 和 WeakMap 的对比示例来加深对“弱引用”的理解。

在对比过程中,需要用到 chrome-devtools 的 memory 面板,在此简单介绍一下,更多介绍访问官网

通过上图操作顺序可以生成当前堆快照:

当想再生成一份快照,点击上图红框标注的按钮即可。

让我们进入正文吧!

Map

在 Chrome 运行如下代码并通过堆快照查看内存消耗。

function Foo() {
    this.val = new Array(10000000).join(",");
}

window.foo = new Foo();
let map = new Map();
map.set(window.foo, 1);

结果如图所示:

然后再进行如下操作,删除 window.foo。

delete window.foo;

再次查看快照:

如上图所示,即使在删除 window.foo 后,该变量也没有被垃圾回收。这是因为虽然 window 中删除了对 foo 的引用,但是 Map 中的引用并没有删除,所以没有释放 foo 的内存。要在 Map 中删除引用,可以创建一个新 Map 对象替换原有对象或 map.delete(window.obj),这样就会释放内存,大家可以试试。

WeakMap

只需对上述代码进行微小改动,将 Map 变为 WeakMap。

function Foo() {
    this.val = new Array(10000000).join(",");
}

window.foo = new Foo();
let map = new WeakMap();
map.set(window.foo, 1);

同样删除 window.foo。

delete window.foo

而 WeakMap 此时的快照如下:

通过上图可以清晰的知道,与 Map 示例相反,一旦我们运行 delete window.foo ,变量就会被垃圾回收。当在 window 中删除了对 foo 的引用,垃圾收集器找不到 foo 的其他引用,所以释放了分配给它的内存。 即使 WeakMap 中有对 foo 的引用,但垃圾收集器仍然释放了内存,这也验证了 WeakMap 中的引用是“弱”的

应用

  • 在 DOM 对象上保存相关数据
  • 数据缓存
  • 私有属性 关于这些应用的具体介绍请查看冴羽大佬的ES6 系列之 WeakMap

参考