在日常业务开发中,发现有用到 WeakMap 对象,然后对其不是太了解,于是就有了这篇文章。本篇文章主要介绍 Map 和 WeakMap 对象的概念、不同以及 WeakMap 对象中的“weak”如何理解。
Map
Map 对象保存键值对,并且能够记住键的原始插入顺序。其中键可以为任何值(对象或原始值)。
Object 和 Map 的比较
键的类型
Map 的键可以是任何值,包括函数、对象或任何基本类型。而 Object 的键必须是一个 String 或 Symbol ,如果向其添加了 Object 或 Array 类型,最终也会被转换为 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。