什么是Set和Map?

Set 是不包含重复值的列表。你一般不会像对待数组那样来访问 Set 中的某个项;相反更常见
的是,只在 Set 中检查某个值是否存在。

Map 则是键与相对应的值的集合。因此,Map中的每个项都存储了两块数据,通过指定所需读取的键即可检索对应的值。 Map 常被用作缓存,存储数据以便此后快速检索。

创建 Set 并添加项目

Set 使用 new Set() 来创建,而调用 add() 方法就能向 Set 中添加项目,检查 size 属性
还能查看其中包含有多少项:

let set = new Set();
set.add(5);
set.add("5");
console.log(set.size); // 2

Set 不会使用强制类型转换来判断值是否重复。这意味着 Set 可以同时包含数值 5 与 字符
串 "5" ,将它们都作为相对独立的项(在 Set 内部的比较使用了第四章讨论过的
Object.is() 方法,来判断两个值是否相等,唯一的例外是 +0 与 -0 在 Set 中被判断为是相
等的)。你还可以向 Set 添加多个对象,它们不会被合并为同一项:

let set = new Set(),
key1 = {},
key2 = {};
set.add(key1);
set.add(key2);
console.log(set.size); // 2

由于 key1 与 key2 并不会被转换为字符串,所以它们在这个 Set 内部被认为是两个不同的
项(记住:如果它们被转换为字符串,那么都会等于 "[object Object]" )。
如果 add() 方法用相同值进行了多次调用,那么在第一次之后的调用实际上会被忽略:

let set = new Set();
set.add(5);
set.add("5");
set.add(5); // 重复了,该调用被忽略
console.log(set.size); // 2

你可以使用数组来初始化一个 Set ,并且 Set 构造器会确保不重复地使用这些值。例如:

let set = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log(set.size); // 5
//数组去重神器!
在此例中,带有重复值的数组被用来初始化这个 Set 。虽然数值 5 在数组中出现了四次,但
Set 中却只有一个 5 。若要把已存在的代码或 JSON 结构转换为 Set 来使用,这种特性会让
转换更轻松。

你可以使用 has() 方法来测试某个值是否存在于 Set 中,就像这样:

let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); // true
console.log(set.has(6)); // false

移除值

也可以从 Set 中将值移除。你可以使用 delete() 方法来移除单个值,或调用 clear() 方法
来将所有值从 Set 中移除。以下代码展示了二者的作用:

let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); // true
set.delete(5);
console.log(set.has(5)); // false
console.log(set.size); // 1
set.clear();
console.log(set.has("5")); // false
console.log(set.size); // 0

Weak Set

Weak Set ,该类型只允许存储对象弱引用,而不能存储基本类型的值。对象的弱引用在它自己成为该对象的唯一引用时,不会阻止垃圾回收

创建 Weak Set

let set = new WeakSet(),
key = {};
// 将对象加入 set
set.add(key);
console.log(set.has(key)); // true
set.delete(key);
console.log(set.has(key)); // false

Set 类型之间的关键差异

Weak Set 与正规 Set 之间最大的区别是对象的弱引用。

let set = new WeakSet(),
key = {};
// 将对象加入 set
set.add(key);
console.log(set.has(key)); // true
// 移除对于键的最后一个强引用,同时从 Weak Set 中移除
key = null;

Weak Set 与正规 Set 的差异,即:

  • 对于 WeakSet 的实例,若调用 add() 方法时传入了非对象的参数,就会抛出错误(
    has() 或 delete() 则会在传入了非对象的参数时返回 false );
  • Weak Set 不可迭代,因此不能被用在 for-of 循环中;
  • Weak Set 无法暴露出任何迭代器(例如 keys() 与 values() 方法),因此没有任何编
    程手段可用于判断 Weak Set 的内容;
  • Weak Set 没有 forEach() 方法;
  • Weak Set 没有 size 属性。

一般来说,若只想追踪对象的引用,应当使用 Weak Set 而不是正规 Set 。

Set总结

  • add():添加
  • delete():删除
  • clear():清空
  • has():是否包含
  • size:包含项的个数
  • forEach():跟数组循环一样
  • 数组去重:

    let set = new Set([1, 2, 3, 3, 3, 4, 5]),
    array = [...set];
    console.log(array); // [1,2,3,4,5]

    Map

Map类型是键值对的有序列表,而键和值都可以是任意类型。键的比较使用的是
Object.is() ,因此你能将 5 与 "5" 同时作为键,因为它们类型不同。这与使用对象属性
作为键的方式(指的是用对象来模拟 Map )截然不同,因为对象的属性会被强制转换为字符
串。

创建Map以及添加数据

调用 set() 方法并给它传递一个键与一个关联的值,来给 Map 添加项;此后使用键
名来调用 get() 方法便能提取对应的值。例如:

let map = new Map();
map.set("title", "Understanding ES6");
map.set("year", 2016);
console.log(map.get("title")); // "Understanding ES6"
console.log(map.get("year")); // 2016

你也可以将对象作为键,这也是从前使用对象属性来创建 Map 的变通方法所无法做到的。例子:

let map = new Map(),
key1 = {},
key2 = {};
map.set(key1, 5);
map.set(key2, 42);
console.log(map.get(key1)); // 5
console.log(map.get(key2)); // 42

Map 的方法

Map 与 Set 共享了几个方法,这是有意的,允许你使用相似的方式来与 Map 及 Set 进行交
互。以下三个方法在 Map 与 Set 上都存在:

  • has(key) :判断指定的键是否存在于 Map 中;
  • delete(key) :移除 Map 中的键以及对应的值;
  • clear() :移除 Map 中所有的键与值。
  • size:包含项目个数
  • forEach:类似set

Map 的初始化

依然与 Set 类似,你能将数组传递给 Map 构造器,以便使用数据来初始化一个 Map 。该数
组中的每一项也必须是数组,内部数组的首个项会作为键,第二项则为对应值。
例如:

let map = new Map([["name", "Nicholas"], ["age", 25]]);
console.log(map.has("name")); // true
console.log(map.get("name")); // "Nicholas"
console.log(map.has("age")); // true
console.log(map.get("age")); // 25
console.log(map.size); // 2

Weak Map

WeakMap 类型是键值对的无序列表,其中键必须是非空的对象,值则允许是任意类
型。 WeakMap 的接口与 Map 的非常相似,都使用 set() 与 get() 方法来分别添加与提取
数据

Weak Map 只有两个附加方法能用来与键值对交互。 has() 方法用于判断指定的键是否存在
于 Map 中,而 delete() 方法则用于移除一个特定的键值对。

当决定是要使用 Weak Map 还是使用正规 Map 时,首要考虑因素在于你是否只想使用对象类
型的键。如果你打算这么做,那么最好的选择就是 Weak Map 。因为它能确保额外数据在不
再可用后被销毁,从而能优化内存使用并规避内存泄漏。

结语

ES6 正式将 Set 与 Map 引入了 JS 。在此之前,开发者往往使用对象来模拟它们,但由于与
对象属性有关的限制,这么做经常会遇到问题。

Set 是无重复值的有序列表。根据 Object.is() 方法来判断其中的值不相等,以保证无重
复。 Set 会自动移除重复的值,因此你可以使用它来过滤数组中的重复值并返回结果。 Set
并不是数组的子类型,所以你无法随机访问其中的值。但你可以使用 has() 方法来判断某个
值是否存在于 Set 中,或通过 size 属性来查看其中有多少个值。 Set 类型还拥有
forEach() 方法,用于处理每个值。

Weak Set 是只能包含对象的特殊 Set 。其中的对象使用弱引用来存储,意味着当 Weak Set
中的项是某个对象的仅存引用时,它不会屏蔽垃圾回收。由于内存管理的复杂性, Weak Set
的内容不能被检查,因此最好将 Weak Set 仅用于追踪需要被归组在一起的对象。

Map 是有序的键值对,其中的键允许是任何类型。与 Set 相似,通过调用 Object.is() 方法
来判断重复的键,这意味着能将数值 5 与字符串 "5" 作为两个相对独立的键。使用
set() 方法能将任何类型的值关联到某个键上,并且该值此后能用 get() 方法提取出来。
Map 也拥有一个 size 属性与一个 forEach() 方法,让项目访问更容易。

Weak Map 是只能包含对象类型的键的特殊 Map 。与 Weak Set 相似,键的对象引用是弱引
用,因此当它是某个对象的仅存引用时,也不会屏蔽垃圾回收。当键被回收之后,所关联的
值也同时从 Weak Map 中被移除。对于和对象相关联的附加信息来说,若要在访问它们的代
码之外对其进行生命周期管理(也就是说,当在对象外部移除对象的引用时,要求其私有数
据也能一并被销毁),则 Weak Map 在内存管理方面的特性让它们成为了唯一合适的选择。