在 ES6 以前,JavaScript 只有数组这一种集合来存储数据(当然有的人会使用对象来存储数据),在 ES6 中引入了两种新的集合来存储数据,它们是 Set 和 Map:
- Set:Set 集合中的数据都不相同
- Map:数据格式为键值对,与对象类似
ES5 中的 Set 和 Map
模拟 Set 和 Map
在 ES5 中,开发者使用对象来模拟 Set 和 Map
let set = Object.create(null); |
上面在 Set 集合中存储了一个名为 foo 的数据,通过将它的值设置为 true 来指明集合中有这个数据。
let map = Object.create(null); |
上述代码创建了一个 map 集合,并且在其中存储 foo-bar 这个键值对。
存在的问题
问题是由于对象的特点引起的,在 JavaScript 中,对象的键值都是字符串
let map = Object.create(null); |
虽然上面是以数字 5 为键值存储的数据,但是在内部会被转化为字符串 “5”,所以数字 5 和字符串 “5” 为键值是一样的。因为对象会将键强转为字符串,所以无法以对象作为键值
let map = Object.create(null); |
上述当我们以 key1 和 key2 为键值存储和访问数据时,都会先将对象转化为字符串,然后以该字符串为键值作为对象的键。key1 和 key2 转化为字符串得到的都是 [object Object],所以以它们作为集合的键是同一个键,无法区分,即以对象模拟 Set 和 Map 无法以对象作为键。
另外对于 map 集合,判断一个键是否存在也存在问题,通常的判断方法如下
let map = Object.create(null); |
上面通过 map.count 来判断 count 键是否存在,但是当 count 的值为 0 时得到的结果也是 false,所以这是一个判断非 0 的逻辑,还是判断键不存在的逻辑,无从得知。
Set
ES6 提供了原生 Set 集合,Set 集合是一个有序的、没有重复元素的集合。
使用 Set
通过 new Set() 得到一个 Set 对象,Set 集合提供如下 API
- add(item):向集合中添加元素 item
- has(item):判断集合中是否有 item 元素
- delete(item):删除集合中的 item 元素
- clear():清空集合中的所有元素
let set = new Set(); |
Set 集合是通过 Object.is 方法来判断两个元素是否相同的,所以 5 和 “5” 是两个不同的元素。如果向 Set 集合中添加相同的元素,那么 Set 集合会忽略重复添加的语句
set.add(6); |
此外,我们还可以像 Set 集合中添加对象
let key1 = {}, |
上面 key1 和 key2 是不同的对象,所以二者都可以被添加进集合中。
console.log(set.has(key1)); // true |
has 方法是判断集合中是否有该元素,而 delete 方法是删除集合中的元素,如果删除成功返回 true,否则返回 false。
set.clear(); |
clear 方法的作用是清空 Set 集合。
遍历 Set
Set 集合提供 forEach 的 API 让我们来遍历 Set 集合,forEach 接收一个回调函数,该回调函数接收三个参数
- 集合中的元素
- 同第一个参数
- set 集合本身
这里比较奇怪的是第二个参数与第一个参数相同,之所以这么做是为了与数组以及 map 集合的 forEach API 保持一致,方便记忆
let set = new Set(); |
如果需要在回调函数中使用 this 的话,可以通过 forEach 的第二个参数将 this 传入
let set = new Set([1, 2]); |
Set 与数组
Set 的构造方法可以接收一个数组作为参数,它会将数组中的元素添加进 Set 集合中,但是不会添加重复的元素
let set = new Set([1, 3, 2, 3, 4]); |
可以通过 ...
运算符将 Set 转化为数组
let set = new Set([1, 3, 2, 3, 4]); |
利用 Set 集合的特点,可以方便的数组进行去重
function elimateDuplicates(items) { |
WeakSet
上面的介绍的 Set 可以看做是 Strong Set,所谓的 Strong 指的是集合中的元素保持对对象的引用,例如
let set = new Set(); |
上面我们为 Set 集合中添加了一个对象 key,随后将对象 key 设置为了 null,但是集合仍然保持着对对象的引用,这就意味着无法回收此对象(这个对象以及无法访问,按道理应该被回收掉),可能会造成内存泄漏的问题。
所以相应的提出了 WeakSet,所谓的 WeakSet 指的是其中的元素没有保持的对对象的引用,如果对象被设置为 null,那么该对象就可以被回收
let weakSet = new WeakSet(); |
WeakSet 提供与 Set 相似的 API
- add
- has
- delete
下面列举 Set 与 WeakSet 的不同:
- WeakSet 集合只能存储对象
- WeakSet 不可遍历,没有 forEach 方法,没有 size 属性
- 没有 clear 方法
Map
使用 Map
Map 是用来存储键值对结构的数据的,我们可以通过 new Map() 创建一个 Map 集合
let map= new Map(); |
Map 提供如下 API
- set(key, value):根据 key 设置 value,如集合中没有该 key,那么添加这一键值对
- get(key):根据 key 查找 value并返回,如果集合中没有该 key,返回 undefined
- has(key):判断集合中有没有 key,有返回 true,否则返回 false
- delete(key):根据 key 删除集合中的键值对,删除成功返回 true,失败返回 false
- clear():清空集合中的所有元素
let map= new Map(); |
Map 集合还可以通过数组进行初始化,它的构造函数接收一个二维数组,形式为 [[key1, value1], [key2, value2]] 的形式
let map= new Map([[5, "Alice"], [10, "Bob"]]); |
遍历 Map
Map 集合也提供了一个 forEach 方法用来遍历 Map 集合,该方法也接受一个回调函数,回调函数接受三个参数
- value
- key
- map 集合本身
let map = new Map([["name", "Alice"], ["age", 18], ["gender", "female"]]); |
如果回调函数中需要用到 this 的话,可以通过 forEach 的第二个参数将 this 传入。
WeakMap
WeakMap 的提出与 WeakSet 相似,都是为了解决垃圾回收问题
let map = new WeakMap(); |
WeakMap 提供以下 API
- set
- get
- has
- delete
let map = new WeakMap(); |
WeakMap 具有如下限制:
- 键必须为对象,否则抛出错误
- 没有 size 属性,无 clear 方法
- 无法使用 forEach 循环,不可迭代