浅析 Map 和 WeakMap 区别以及使用场景
Posted 丞丞同学
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析 Map 和 WeakMap 区别以及使用场景相关的知识,希望对你有一定的参考价值。
希望这一篇文章能让你对 Map
有更好的理解,或者能够帮你理解 Map
和 WeakMap
这篇文章会先从Map
再到WeakMap
一、为什么是 Map ?
1. 传统对象结构
Map
本质上是一个键值对的集合。和传统对象结构相比,传统的对象只能用字符串作为键名,这就在使用上造成了很大的限制了。这也是新增 Map
的原因之一。
const data = {};
// element 为节点对象
const element = document.querySelector(".node");
console.log(element); // 输出 div.node 对象
// 将对象转化成字符串输出 [object htmlDivElement]
console.log(element.toString());
// 用点操作符不能有空格,所以采用中括号的形式给对象赋值
data[element] = \'objectData\'
// 输出 objectData,说明在对象中存在[object HTMLDivElement]键名
console.log(data[\'[object HTMLDivElement]\']);
在上面的代码中,我们创建了一个对象并将一个节点对象作为了它的键名,并进行了代码测试,首先验证了获取到的element
节点为一个对象,再确定了经过toString
方法转化后的结果,以这个值为键名成功的输出了value
值objectData
通过上面的测试,确定了传统对象的键名会通过toString
方法转化为字符串类型
2. Map 结构
Map
类似于对象,但是键名不限于字符串,可以说Object
结构提供键-值对应,Map
结构提供值-值对应因此其实采用map
结构会优于传统对象
// 1. 通过new Map来创建dataMap容器
const dataMap = new Map();
// 2. 获取节点对象,作为测试数据
const element = document.querySelector(".node");
// 3. 通过 set 方法给 dataMap 中指定键和对应的值
dataMap.set(element,\'objectData\');
// 4. 通过 get 来从 dataMap 中获取键名对应的值
console.log(dataMap.get(element));
// 5. 揭开面目
console.log(dataMap);
从上面的代码中,我们可以清楚的看到,第8行代码获取值时直接传入了element
对象,
可以成功的获取到对应的值,在最后打印dataMap
时更是验证了上诉说法
成功的将对象作为了键名,弥补了传统对象的不足
3. Map 的特点
-
Map 默认情况下不包含任何键,所有键都是自己添加进去的。不同于 Object 原型链上有一些默认的键。
-
Map 的键可以是任意类型数据,就连函数都可以。
-
Map 的键值对个数可以轻易通过
size
属性获取,Object 需要手动计算。 - Map 在频繁增删键值对的场景下性能要比 Object 好。
4. 什么时候用 Map
- 要添加的键值名和 Object 上的默认键值名冲突,又不想改名时,用 Map
- 需要 String 和 Symbol 以外的数据类型做键值时,用 Map
- 键值对很多,有需要计算数量时,用 Map
- 需要频繁增删键值对时,用 Map
二、Map 实例属性和方法
在上面我们已经接触到了Map
的个别 API,接下来简单说说
1. set
set
方法设置键名key
对应的键值为value
,然后会返回整个Map
结构,如果设置的key
已经存在,则会更新value
值,否则会新生成该键
也可以采用链式写法设置多组数据
成功输出如下:
2. get
通过get
方法读取key
对应的键值,如果传入的键值不存在,则会返回undefined
控制台成功输出ljc
3. has
判断传入的键是否存在当前Map
对象中,该方法返回一个布尔值
在上面的代码中,存在name
为true
,不存在sex
返回false
4. delete
删除传入的键,返回true
,如果删除失败,则返回false
5. clear
清除所有成员,没有返回值
clear
前后结果对比,注意clear
没有返回值!
三、遍历方法
可以采用for...of
循环和forEach
两种方法。由于Map
实例会维护键值对的插入顺序,因此可以根据插入顺序进行遍历
采用for...of
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每个成员
map.entries()
在Map
实例中有一个迭代器,能以插入顺序生成[key,value]
形式的数据。
我们可以通过entries
方法来获得这个迭代器,从而利用for...of
进行遍历操作
也可以采用如下进行遍历,每次item
获取到一个数组
又因为entries
是默认的迭代器,所以可以直接对Map
实例使用扩展操作或者直接采用map
采用扩展操作
map.values()
可以采用遍历map.values()
的方式来遍历map
容器的属性值
map.keys()
可以采用map.keys()
来遍历键名
采用 forEach() 回调遍历
通过回调的方式遍历map
四、Map 类型转化
几种与map
相互类型转化的方法
Map 转为数组
通过扩展运算符实现
let map = new Map()
let arr = [...map]
数组转为 Map
let map = new Map(arr)
Map 转为对象
通过遍历利用set
将键值对加入对象中
let obj = {}
for (let [k, v] of map) {
obj[k] = v
}
对象转为 Map
for( let k of Object.keys(obj)){
map.set(k,obj[k])
}
五、什么是 WeakMap ?
总所周知,WeakMap
是 ES6 中新增的一种集合类型,叫做“弱映射”。它和Map
是兄弟关系,与Map
的区别就在于这个弱字,API 还是Map
的那套(只有set
get
has
delete
)
那它真正是什么意思呢?
那为什么要有 WeakMap 呢?它解决了什么问题呢?这些问题后面都会讲到
六、WeakMap 的特性
我们先从 WeakMap 的特性讲起
1. WeakMap 只能将对象作为键名
- 只接受对象作为键名(
null
除外),不接受其他类型的值作为键名
null 除外
正常添加
2. WeakMap 的键名引用的对象是弱引用
这里懵了挺久的,但是这是WeakMap
结构的关键所在
要想读懂这句话,不容易,我们需要先知道强引用和弱引用
2.1 什么是强引用?
我们先来看看强引用,这是阮一峰老师书上的例子
在上面的代码中,e1
和e2
是两个对象,通过arr
数组对这两个对象添加一些文字说明。但是这样就形成了arr
对e1
和e2
的引用,而这种引用又是强引用。它的区别就体现在。当我们不再需要这两个对象时,我们必须手动的删除这个引用,解除arr
都两个对象的引用关系,否则垃圾回收机制不会释放e1
和e2
占用的内存。因为,arr
仍然存在着对对象的引用!
麻烦的操作势必会造成问题,当忘记了手动删除引用,就会造成内存泄漏
2.2 什么是弱引用?
对于弱引用,百度百科给出的答案:
也就是说如果我们能这样创建一个弱引用的对象
//假设
let obj = new WeakObject()
我们就可以静静的等待垃圾车来把它拖走了,obj
所引用的对象就会被回收
如果还没有理解的话,我们再来看看
2.3 弱引用和强引用图解
从1套代码结合两张图来理解
对于强引用
const myMap = new Map()
let my = {
name: "ljc",
sex: "男"
}
myMap.set(my, \'info\');
console.log(myMap);
对于弱引用
const myMap = new WeakMap()
let my = {
name: "ljc",
sex: "男"
}
myMap.set(my, \'info\');
console.log(myMap);
图一中的数据被my
和myMap
实例对象所引用,引用计数为 2,图2中建立了myMap
对my
所引用的对象的弱引用,引用计数为 1
在上面我们谈到强引用数据被删除时,需要手动解除引用,而弱引用则可以等待垃圾回收机制自动清除
弱引用与垃圾回收
当执行my = null
时会解除my
对原数据的引用,而myMap
实例对象对my
所引用对象是弱引用关系,该数据的引用计数为 0 ,程序垃圾回收机制在执行时会将引用对象回收。而如果时强引用关系则引用计数为 1 ,不会被垃圾回收机制清除。
3. 不可遍历
正因为WeakMap
对键名所引用的对象是弱引用关系,因此WeakMap
内部成员是会却决于垃圾回收机制有没有执行,运行前后成员个数很可能是不一样的,而垃圾回收机制的执行又是不可预测的,因此不可遍历
七、Map 和 WeakMap 的区别
Map
的键可以是任意类型,WeakMap
只接受对象作为键(null除外),不接受其他类型的值作为键Map
的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键;WeakMap
的键是弱引用,键所指向的对象可以被垃圾回收,此时键是无效的Map
可以被遍历,WeakMap
不能被遍历
八、WeakMap 的使用场景
1. DOM 节点元数据
因为 weakMap 不会影响垃圾回收,所以可以用来关联元数据
当上面代码执行后,登录按钮从DOM树中被删除了,但由于 Map 对节点对象是强引用关系,仍然保存着对按钮的引用,所以会引起内存泄漏
因此可以采用WeakMap
当节点删除后,引用计数为0,等待垃圾回收机制回收
2. 部署私有属性
利用弱映射,将内部属性设置为实例的弱引用对象,当实例删除时,私有属性也会随之消失,因此不会内存泄漏
阮一峰老师的代码实例
3. 数据缓存
当我们需要在不修改原有对象的情况下储存某些属性等,而又不想管理这些数据时,可以使用WeakMap
以上是关于浅析 Map 和 WeakMap 区别以及使用场景的主要内容,如果未能解决你的问题,请参考以下文章