ES6 新增了一种类似于数组的数据结构,和数组的区别在于 Set 中没有重复值。本篇分享将通过各种示例进行认识和使用 Set 这一数据结构。 因为和数组结构相似,可以从和数组的异同点来对比理解 Set 的特性。

创建 Set 数据

因为 Set 本身是一个构造函数,可以直接使用new命令来创建 Set 数据。创建时可以接受一个字符串,数组或者其他实现了 iterable 接口的可遍历数据。

  1. const data = new Set([iterable]);
  1. 创建空 Set 再通过add方法添加数据

add方法返回该 Set 对象,可以链式调用。

  1. const myset = new Set();
  2. myset.add("AA").add("BB");
  3. console.info("myset", myset); // 得到 Set { 'AA', 'BB' }
  1. 传入可遍历数据
    1. const myset = new Set([1, 2, 3]);
    2. console.info("myset", myset);
    3. console.info("myset", myset); // 得到 Set { 1, 2, 3 }

    Set 中元素唯一

    当向 Set 中添加多个元素的时候,重复的元素会被去除,不会被添加到同一个 Set 数据中,这包括数组的重复元素和字符串的重复字符。
    1. const myset = new Set([1, 2, 2, 3, 3, 3, 3]);
    2. // myset 依然是 Set { 1, 2, 3 }
    3. const myset2 = new Set('aaac')
    4. // myset2 是 Set { 'a', 'c' }
    添加到同一个 Set 数据的时候,数据类型不会发生转换,意味着你可以同时添加number类型的1string类型的'1'。所以添加元素的时候,会先和 Set 数据中已有元素对比,对比规则是 same-value同值对比,规则和===运算符相似。不同的是正常NaN===NaN会返回false,但这里认为NaN等于自身,所以 Set 数据中只会有一个NaN变量。
    而对于引用类型的变量,比如对象或数组,则总是不相等的。
    1. const myset = new Set();
    2. myset.add([]).add([]);
    3. console.info("myset", myset); // 得到 Set { [], [] }

    Set 对象属性和方法

    属性和方法决定了怎么去使用这个数据结构。来看看Set.prototype 上都有哪些属性和函数。

    size 属性

    除掉构造函数,Set 数据的常用属性就是 size,Set.prototype.size。该属性可以返回该 Set 结构的元素个数。
    1. const myset = new Set([1, 2, 2]);
    2. const count = myset.size;
    3. console.info("count", count); // 得到 count 2

    Set 结构的数据操作方法

    Set 结构的数据操作有增 删 查 清四种,对应如下方法:
  • add() 添加元素,返回 Set 本身,可链式调用
  • delete() 删除元素,返回boolean值表示删除是否成功
  • has() 返回boolean值表示是否含有该元素
  • clear() 清除 Set 结构中所有元素,无返回值

通过一个示例来查看:

  1. const myset = new Set();
  2. myset.add("1").add("2").add("2");
  3. console.info("size", myset.size);
  4. console.info(myset.has("1"));
  5. console.info(myset.delete("2"));
  6. console.info("size", myset.size);
  7. myset.clear();
  8. console.info(myset);

得到输出:

  1. size 2
  2. true // has 方法返回
  3. true // delete 方法返回
  4. size 1
  5. Set {} //删除之后空的 Set

Set 结构的遍历方法

对于一个集合,总会有将其中的一个或多个元素取出操作的需要,就需要取值和遍历。

  • keys() 返回键名的迭代器
  • values() 返回值的迭代器
  • entries() 返回键值对的迭代器
  • forEach() 使用回调函数遍历每个元素

(这里需要对用于遍历的Iterator迭代器有基本了解)
因为 Set 的元素是单值的,并没有对象中属性名和值的一一对应关系,所以,Set 中通过 keys() 返回的键名和 values() 返回的值的结果是一样的,同样entries()获得多个键名和值名的组成的数组,数组中两个元素一样。使用forEach方法的时候,第一个参数函数的前两个参数(值和键名)也是一样的。

  1. const myset = new Set();
  2. myset.add("AA").add("BB");
  3. for (const item of myset.keys()) {
  4. console.info(item);
  5. }
  6. for (const item of myset.values()) {
  7. console.info(item);
  8. }
  9. //上面两种遍历输出一样
  10. // AA
  11. // BB
  12. for (const item of myset.entries()) {
  13. console.info(item);
  14. }
  15. // [ 'AA', 'AA' ]
  16. // [ 'BB', 'BB' ]
  17. myset.forEach((element) => {
  18. console.info("hello:" + element);
  19. });
  20. // hello:AA
  21. // hello:BB

除了这些方法,Set 结构默认就是可遍历的,默认的迭代器函数就是 values() 方法,这样就可以直接for...of循环遍历,不用调用其他方法

  1. //上面的 myset 变量,直接用 for...of 遍历
  2. for (const item of myset) {
  3. console.info(item);
  4. }

和数组的转化

对于一个数组,可以通过new Set()传入作为参数,得到 Set 结构。如果还要转回来数组呢?使用遍历的方式可行,但更推荐两种简单易行的方式:

  1. 扩展运算符

扩展运算符内部使用的是for...of循环,可直接用于 Set 结构。

  1. const myset = new Set([1, 2, 3]);
  2. const arr = [...myset];
  3. console.info("arr", arr); // arr [ 1, 2, 3 ]
  1. Arrray.from方法

Array.from 可以接受 Set 结构的参数,转化为了一个数组。

  1. const myset = new Set([1, 2, 3]);
  2. const arr = Array.from(myset);
  3. console.info("arr", arr); // arr [ 1, 2, 3 ]

应用

了解了 Set 数据的特性和 API 方法,可以再深入了解 Set 常用的场景。

数组,字符串等数据的去重

如上所述,因为 Set 结构的值是唯一的,所以对于元素值重复情况不确定的数组,可以通过转 Set 结构,再转回数组的方式来实现去重。

  1. const array = [1, 1, 2, 3, 3];
  2. const newArray = [...new Set(array)];
  3. console.info(newArray);

同样的,先展开字符串,添加到 Set 结构,再拼接回来,也可以去除重复字符,得到新的不带重复字符的字符串。

  1. const str = "aaaadcc";
  2. const newString = [...new Set(str)].join("");
  3. console.info(newString); // adc

同样的,凡是需要不能存在重复元素的情况,都可以使用 Set 结构做一层中转以去重。

实现并集交集和差集

阮一峰老师的 Set 实现并集·交集·差集这部分内容,将 Set 结构的特性 和 API 的使用完美配合。 上代码:

  1. let a = new Set([1, 2, 3]);
  2. let b = new Set([4, 3, 2]);
  3. // 并集
  4. let union = new Set([...a, ...b]);
  5. // Set {1, 2, 3, 4}
  6. // 交集
  7. let intersect = new Set([...a].filter(x => b.has(x)));
  8. // set {2, 3}
  9. // (a 相对于 b 的)差集
  10. let difference = new Set([...a].filter(x => !b.has(x)));
  11. // Set {1}

将两个 Set 结构展开,放到同一个数组再传给 new Set(),重复元素被去除,得到并集。
其中一个 Set 结构 a 转数组,调用filter方法得到 a 中 存在 b 中也存在的元素,返回在两个 Set 中都存在的元素,得到交集。
其中一个 Set 结构 a 转数组,调用filter方法得到在 a 中存在,b 中不存在的元素,将 a 中相对于 b 重复的元素去除,得到差集。