引言
Map(映射)和Set(集合)是ES6引入的数据结构,它们提供了更灵活、高效的方式来存储和访问数据。本文将介绍一下这两种数据结构以及WeakMap(弱映射)和WeakSet(弱集合)这两种新的数据结构的概念及使用。
Map
Map是一种以键值对的形式存储数据的集合。它的结构类似于对象,它也是键值对的集合,但是Map的键可以是任何类型的值,包括对象、函数和原始类型等。可以说Map就是对象的一种扩展。
特点
- 键是唯一的,后添加的键值对会替换前面的
- 键值是有序的,使用Map对象将按照插入顺序得到每一个键值对
- 各种类型的值都可以当作键
属性及方法
创建
我们可以通过下面的代码创建一个空的Map
const map = new Map() console.log(map); // Map(0) {}
赋予初始值:map使用 [ ] 表示层级结构,第一维度是映射的个数,第二维度是具体的值,结构是 [key, value]
const map = new Map<string, string | number>([['name', '阿黄'], ['age', 20]]) console.log(map); // Map(2) { 'name' => '阿黄', 'age' => 20 }
新增
通过set(key, value)来增加一个键值对,如果键已存在,则会更新对应的值
const map = new Map() map.set('name', '阿黄') console.log(map);// Map(1) { 'name' => '阿黄' } map.set('name', '小黑') console.log(map);// Map(1) { 'name' => '小黑' }
获取
使用get(key)返回key对应的值,如果值不存在则返回undefined
const map = new Map() console.log(map.get('name')); // undefined map.set('name', '阿黄') console.log(map.get('name')); // 阿黄
是否存在值
使用map.has(key)可以判断是否存在键对应值
const map = new Map() console.log(map.has('name')); // false map.set('name', '阿黄') console.log(map.has('name')); // true
删除
通过delete(key)来删除map的值
const map = new Map() map.set('name', '阿黄') console.log(map); // Map(1) { 'name' => '阿黄' } map.delete('name') console.log(map); // Map(0) {}
清空
使用map.clear()函数清空map
const map = new Map() map.set('name', '阿黄') map.set('age', 12) console.log(map); // Map(2) { 'name' => '阿黄', 'age' => 12 } map.clear() console.log(map); // Map(0) {}
获取长度
map可以通过size获取子集个数
const map = new Map() map.set('name', '阿黄') map.set('age', 12) console.log(map.size); // 2
键的迭代器
使用keys函数可以返回Map中所有的键的迭代器
const map = new Map() map.set('name', '阿黄') map.set('age', 12) console.log(map.keys()); // { 'name', 'age' }
值的迭代器
和keys类似,values函数可以返回Map中所有的值的迭代器
const map = new Map() map.set('name', '阿黄') map.set('age', 12) console.log(map.keys()); // { 'name', 'age' }
键值对的迭代器
使用entries或者是Symbol.iterator函数可以返回Map的键值对
const map = new Map() map.set('name', '阿黄') map.set('age', 12) console.log(map.entries()); // { [ 'name', '阿黄' ], [ 'age', 12 ] } console.log(map[Symbol.iterator]()); // { [ 'name', '阿黄' ], [ 'age', 12 ] }
遍历
使用forEach函数进行遍历,和数组的forEach类似(数组第二项是索引),callback函数返回三个值,分别是value,key,map
const map = new Map() map.set('name', '阿黄') map.set('age', 12) map.forEach((val, key, map) => { console.log(val, key, map); }) // 阿黄 name Map(2) { 'name' => '阿黄', 'age' => 12 } // 12 age Map(2) { 'name' => '阿黄', 'age' => 12 }
使用场景
描述映射关系
在对象中我们只能使用string或symbol来描述键的值,而Map可以很好的描述关系映射
const map = new Map() map.set(['meat', 'fish'], '都是阿黄爱吃的') console.log(map); // Map(1) { [ 'meat', 'fish' ] => '都是阿黄爱吃的' }
缓存
我们使用Map的索引唯一性特点将其运用于缓存操作,下面的代码中我们使用一个函数作为map的键,第一次调用函数会触发set操作,后续操作会直接使用get获取对应值
const cache = new Map(); const temp = (key: any) => cache.get(key) ?? (cache.set(key, getNum(1, 2)), cache.get(key))// 缓存操作,首次访问会执行set操作 const getNum = (a: number, b: number) => a + b console.log(temp(getNum)) console.log(temp(getNum))
Set
Set是一种无序且唯一的集合(可以当成一个数学集合),它可以存储任何类型的值,包括对象、函数和原始类型等。它的结构类似数组,但是其中的值不允许重复,这点与Map的键值类似。
特点
- 成员的值都是唯一的
- 值的无序性(数据存储不依赖索引,而是哈希表)
属性及方法
创建及初始化
const set = new Set<string>() console.log(set);// Set(0) {}
初始化可以使用[ value ]或任意数组进行赋值
const set = new Set<string>(['阿黄']) console.log(set);// Set(1) { '阿黄' }
新增
Set的新增子项与Map不太一样,可以使用add函数进行操作
const set = new Set(['阿黄']) set.add('小黑') console.log(set);// Set(2) { '阿黄', '小黑' }
删除
set使用delete对集合的单项进行删除
const set = new Set<string>(['阿黄']) set.delete('阿黄') console.log(set);// Set(0) {}
其他属性
剩下的属性与Map几乎相同,用法上就不做赘述。此外,Set的迭代器方法比较特殊,使用set.values()和set.keys()的结果相同,因为Set的key和value都是value值
const set = new Set<string>(['阿黄']) console.log(set.values()); // [Set Iterator] { '阿黄' } console.log(set.keys()); // [Set Iterator] { '阿黄' } console.log(set.entries()); // [Set Entries] { ['阿黄', '阿黄'] }
使用场景
数组去重
const set = new Set([6, 6, 4, 2, 3, 4, 3, 3, 1, 5, 5, 2]); console.log(set);// Set(6) { 6, 4, 2, 3, 1, 5 }
集合操作
交集,并集,差集
const set = new Set([1, 2, 3, 4, 5]); const set2 = new Set([1, 3, 5, 7, 9]); // 交集 console.log([...set].filter(it => set2.has(it))); // 并集 console.log(new Set([...set, ...set2])); // 差集 console.log([...set].filter(it => !set2.has(it)));
WeakMap
在了解WeakMap之前,先看段代码
let obj: any = { name: "阿黄" } const map = new Map([[obj, "阿黄"]]); obj = null console.log(map);// Map(1) { { name: '阿黄' } => '阿黄' }
上面的代码中我们新建了一个object类型的变量,使其作为键赋值到Map中,当我们把obj删除时,Map中的键值没有发生变化,说明其二者是深复制关系,此时的obj存在与Map中不会被垃圾回收机制处理。此时如果我们想清除这个obj在Map中的引用对象,就可以使用WeakMap。
回到正题,WeakMap是一种弱引用的哈希表对象,就像上面的例子,如果键对象不再被引用,它将被垃圾回收并从WeakMap中自动删除,从而避免了内存泄漏的问题
特点
- 键名只能是对象
- 键名使用弱引用
属性及方法
创建及初始化
这与Map相似,只不过键名只能传对象类型的
let obj: any = { name: "阿黄" } const weakMap = new WeakMap([[obj, "阿黄"]]);
其他属性
WeakMap只拥有Map的get、set、has、delete这几个方法,不包含iterator迭代属性,也就是说clear需要手动一个一个删除
const obj = { name: "阿黄" } const obj2 = { name: "小黑" } const weakMap = new WeakMap([[obj, "阿黄"]]); weakMap.set(obj2, "小黑") weakMap.delete(obj) console.log(weakMap.has(obj)); // false console.log(weakMap.get(obj2)); // 小黑
使用场景
属性私有化
使用WeakMap可以对类的属性进行封装
const animalPrivate = new WeakMap() class Animal { constructor() { animalPrivate.set(this, { name: "阿黄" }) } getName() { return animalPrivate.get(this).name } } console.log(new Animal().getName());
避免内存泄漏
与Map效果不同,关联的object删除后weakMap键名也会删除
let obj: any = { name: "阿黄" } const weakMap = new WeakMap([[obj, "阿黄"]]); console.log(weakMap.get(obj)); // 阿黄 obj = null console.log(weakMap.get(obj)); // undefined
WeakSet
WeakSet和WeakMap一样,也可以避免内存泄漏,它的每一项只能传对象类型
特点
- 值只能是对象
- 值使用弱引用
属性及方法
创建及初始化
const obj = { name: "阿黄" } const weakSet = new WeakSet([obj]);
其他属性
WeakSet也是Set的简化版,只有add,delete,has三个属性,和WeakMap一样不支持iterator属性,无法迭代
const obj = { name: "阿黄" } const obj2 = { name: "小黑" } const weakSet = new WeakSet([obj]); weakSet.add(obj2) weakSet.delete(obj) console.log(weakSet.has(obj));// false
使用场景
避免内存泄漏
这点和WeakMap一样
let obj: any = { name: "阿黄" } const weakSet = new WeakSet([obj]); obj = null console.log(weakSet.has(obj));// false
总结
本文主要介绍了ES6引入的四种新的数据结构,分别是Map(映射)、Set(集合)、WeakMap(弱映射)和WeakSet(弱集合)。Map键可以是任意类型,唯一且有序;Set键值合一并可以是任意值,唯一且无序;WeakMap是键限制为对象的青春版Map;WeakSet是值为对象的迷你版Set,Weak的二者的键被垃圾回收后即刻消失。
以上就是文章全部内容,如果文章有帮到你或觉得文章不错的,还请支持一下作者,谢谢!