ES6-Map和WeakMap

472 阅读5分钟

这是我参与新手入门的第三篇文章。

前言

MapWeakMap 是Es6新增的一种集合类型。采用键值对存储,可能你这时会想,对象 Object 也是采用键值对存储,为什么还需要这两个MapWeakMap的键可以是对象,而对象的键只能是字符串。这无疑为 MapWeakMap 增加了许多可能性。

一丶使用

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")

二丶区别

你会发现在使用上 WeakMapMap 并无太大差异,但需要注意的是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) 时,其实建立了 weakMapkey 所引用的对象的弱引用,但是 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 实例不会妨碍垃圾回收,所以非常适合保存关联元数据。

使用了常规的 Mapconst 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()

参考: segmentfault.com/a/119000001…