这是我参与新手入门的第三篇文章。
前言
Map 和 WeakMap 是Es6新增的一种集合类型。采用键值对存储,可能你这时会想,对象 Object 也是采用键值对存储,为什么还需要这两个Map和WeakMap的键可以是对象,而对象的键只能是字符串。这无疑为 Map 和 WeakMap 增加了许多可能性。
一丶使用
1. Map的使用
声明
const map = new Map() // 空Map
设置值
map.set("key","value")
取值
map.get("key");
判断key是否存在
map.has("key")
删除key
map.delete("key")
循环遍历map
map.forEach(function(key){
console.log("key",key) //输出的是map中的value值
})
2. WeakMap的使用
声明
const weakMap = new WeakMap();
设置值
let key = {}
weakMap.set(key,"value")
取值
weakMap.get("key")
判断key是否存在
weakMap.get("key")
二丶区别
你会发现在使用上 WeakMap 和 Map 并无太大差异,但需要注意的是WeakMap 存储的时候必须是对象,或者继承于 Object 的类型。
只要有一个键无效就会抛出错误,导致整个初始化失败
const weakMap2 = new WeakMap([
[key1, "val1"],
["BADKEY", "val2"],
[key3, "val3"]
]);
// TypeError: Invalid value used as WeakMap key
typeof weakMap2;
// ReferenceError: weakMap2 is not defined
WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。
这意味着,我们无法对其进行枚举并且获得其 values。
那什么又是弱引用呢?
如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存。
对于不再使用的对象,可以使用 null 来覆盖对应对象的引用。
let obj = { key: "value" };
// obj是它的引用
obj = null; // 销毁引用
// 该对象将会被从内存中清除
但是如果我们把对象存到数组里,单单把对象销毁,不销毁数组还是可以通过数组去到对象的值。
let obj = { key: "value" };
let array = [ obj ];
obj = null; // 销毁引用
// obj 被存储在数组里, 所以它不会被垃圾回收机制回收
// 我们可以通过 array[0] 来获取它
同样,如果我们使用对象作为常规 Map 的键,那么当 Map 存在时,该对象也将存在。它会占用内存,并且不会被垃圾回收机制回收。
let obj = { key: "value" };
let map = new Map();
map.set(obj, "mapValue");
obj = null; // 销毁引用
// obj被存储在map中
// 使用map.keys()来获取
如果我们使用 WeakMap 的话再来看看。
let obj = { key: "value" }
let weakMap = new WeakMap()
weakMap.set(obj, "mapValue")
obj = null; // 销毁引用
// 使用weakMap.keys() weakMap.keys is not a function
三丶测试 WeakMap和 Map。
node --expose-gc
global.gc()
process.memoryUsage(); // heapUsed: 4640360 ≈ 4.4M
let map = new Map()
let key = new Array(60000)
map.set(key, 1)
global.gc()
process.memoryUsage() // heapUsed: 注意这里大约是 44.6M
key = null
global.gc()
process.memoryUsage() // heapUsed: 46754648 ≈ 44.6M
map.delete(key) // 这句话其实是无用的,因为 key 已经是 null 了
global.gc()
process.memoryUsage() // heapUsed: 大约是 44.6M
如果你想要让 Obj 被回收掉,你需要先 delete(key) 然后再 key = null。
再次测试。
let map = new Map()
let key = new Array(60000)
map.set(key, 1)
map.delete(key)
key = null
node
node --expose-gc
global.gc();
process.memoryUsage() // heapUsed: 4638376 ≈ 4.4M
let map = new Map()
let key = new Array(60000)
map.set(key, 1)
global.gc()
process.memoryUsage();// heapUsed: 46727816 ≈ 44.6M
map.delete(key)
global.gc()
process.memoryUsage() // heapUsed: 46748352 ≈ 44.6M
key = null;
global.gc();
process.memoryUsage() // heapUsed: 4808064 ≈ 4.6M
再看下weakMap。
const weakMap = new WeakMap()
let key = new Array(5 * 1024 * 1024)
weakMap.set(key, 'value')
key = null
当我们设置 weakMap.set(key, 'value) 时,其实建立了 weakMap 对 key 所引用的对象的弱引用,但是 let key = new Array(60000) 建立了 key 对所引用对象的强引用, 所以被引用的对象并不会被回收,但是当我们设置 key = null 的时候,就只有 weakMap 对所引用对象的弱引用,下次垃圾回收机制执行的时候,该引用对象就会被回收掉。
node测试
node --expose-gc
global.gc()
process.memoryUsage() // heapUsed: 大约 4M左右
const weakMap = new WeakMap()
let key = new Array(5 * 1024 * 1024)
weakMap.set(key, 1)
global.gc()
process.memoryUsage() // heapUsed: 大约 44.6M
key = null
global.gc()
process.memoryUsage() // heapUsed: 大约 4M 左右
所以 WeakMap 可以帮你省掉手动删除对象关联数据的步骤。WeakMap 保持了对键名所引用的对象的弱引用,即垃圾回收机制不将该引用考虑在内。只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。
四丶使用场景
DOM 节点元数据
因为 WeakMap 实例不会妨碍垃圾回收,所以非常适合保存关联元数据。
使用了常规的 Map:
const m = new Map();
const loginButton = document.querySelector('#login');
// 给这个节点关联一些元数据
m.set(loginButton, {disabled: true});
假设在上面的代码执行后,页面被 JavaScript 改变了,原来的登录按钮从 DOM 树中被删掉了。但 由于映射中还保存着按钮的引用,所以对应的 DOM 节点仍然会逗留在内存中,除非明确将其从映射中 删除或者等到映射本身被销毁。
如果这里使用的是弱映射,如以下代码所示,那么当节点从 DOM 树中被删除后,垃圾回收程序就 可以立即释放其内存(假设没有其他地方引用这个对象)
const weakMap = new WeakMap();
const loginButton = document.querySelector('#login')
// 给这个节点关联一些元数据
weakMap.set(loginButton, {disabled: true})
五丶总结
weakMap是为开发者提供了主动解决内存回收的方式。weakMap不能包含无引用的对象,否则会被自动清除。WeakMap对象是不可枚举的,无法获取集合的大小。WeakMap只有四个方法可用:get()、set()、has()、delete()。