ES6 新增了一种类似于数组的数据结构,和数组的区别在于 Set 中没有重复值。本篇分享将通过各种示例进行认识和使用 Set 这一数据结构。 因为和数组结构相似,可以从和数组的异同点来对比理解 Set 的特性。
创建 Set 数据
因为 Set 本身是一个构造函数,可以直接使用new命令来创建 Set 数据。创建时可以接受一个字符串,数组或者其他实现了 iterable 接口的可遍历数据。
const data = new Set([iterable]);
- 创建空 Set 再通过
add方法添加数据
add方法返回该 Set 对象,可以链式调用。
const myset = new Set();myset.add("AA").add("BB");console.info("myset", myset); // 得到 Set { 'AA', 'BB' }
- 传入可遍历数据
const myset = new Set([1, 2, 3]);console.info("myset", myset);console.info("myset", myset); // 得到 Set { 1, 2, 3 }
Set 中元素唯一
当向 Set 中添加多个元素的时候,重复的元素会被去除,不会被添加到同一个 Set 数据中,这包括数组的重复元素和字符串的重复字符。
添加到同一个 Set 数据的时候,数据类型不会发生转换,意味着你可以同时添加const myset = new Set([1, 2, 2, 3, 3, 3, 3]);// myset 依然是 Set { 1, 2, 3 }const myset2 = new Set('aaac')// myset2 是 Set { 'a', 'c' }
number类型的1和string类型的'1'。所以添加元素的时候,会先和 Set 数据中已有元素对比,对比规则是same-value同值对比,规则和===运算符相似。不同的是正常NaN===NaN会返回false,但这里认为NaN等于自身,所以 Set 数据中只会有一个NaN变量。
而对于引用类型的变量,比如对象或数组,则总是不相等的。const myset = new Set();myset.add([]).add([]);console.info("myset", myset); // 得到 Set { [], [] }
Set 对象属性和方法
属性和方法决定了怎么去使用这个数据结构。来看看Set.prototype上都有哪些属性和函数。size 属性
除掉构造函数,Set 数据的常用属性就是size,Set.prototype.size。该属性可以返回该 Set 结构的元素个数。const myset = new Set([1, 2, 2]);const count = myset.size;console.info("count", count); // 得到 count 2
Set 结构的数据操作方法
Set 结构的数据操作有增 删 查 清四种,对应如下方法:
- add() 添加元素,返回 Set 本身,可链式调用
- delete() 删除元素,返回
boolean值表示删除是否成功 - has() 返回
boolean值表示是否含有该元素 - clear() 清除 Set 结构中所有元素,无返回值
通过一个示例来查看:
const myset = new Set();myset.add("1").add("2").add("2");console.info("size", myset.size);console.info(myset.has("1"));console.info(myset.delete("2"));console.info("size", myset.size);myset.clear();console.info(myset);
得到输出:
size 2true // has 方法返回true // delete 方法返回size 1Set {} //删除之后空的 Set
Set 结构的遍历方法
对于一个集合,总会有将其中的一个或多个元素取出操作的需要,就需要取值和遍历。
- keys() 返回键名的迭代器
- values() 返回值的迭代器
- entries() 返回键值对的迭代器
- forEach() 使用回调函数遍历每个元素
(这里需要对用于遍历的Iterator迭代器有基本了解)
因为 Set 的元素是单值的,并没有对象中属性名和值的一一对应关系,所以,Set 中通过 keys() 返回的键名和 values() 返回的值的结果是一样的,同样entries()获得多个键名和值名的组成的数组,数组中两个元素一样。使用forEach方法的时候,第一个参数函数的前两个参数(值和键名)也是一样的。
const myset = new Set();myset.add("AA").add("BB");for (const item of myset.keys()) {console.info(item);}for (const item of myset.values()) {console.info(item);}//上面两种遍历输出一样// AA// BBfor (const item of myset.entries()) {console.info(item);}// [ 'AA', 'AA' ]// [ 'BB', 'BB' ]myset.forEach((element) => {console.info("hello:" + element);});// hello:AA// hello:BB
除了这些方法,Set 结构默认就是可遍历的,默认的迭代器函数就是 values() 方法,这样就可以直接for...of循环遍历,不用调用其他方法。
//上面的 myset 变量,直接用 for...of 遍历for (const item of myset) {console.info(item);}
和数组的转化
对于一个数组,可以通过new Set()传入作为参数,得到 Set 结构。如果还要转回来数组呢?使用遍历的方式可行,但更推荐两种简单易行的方式:
- 扩展运算符
扩展运算符内部使用的是for...of循环,可直接用于 Set 结构。
const myset = new Set([1, 2, 3]);const arr = [...myset];console.info("arr", arr); // arr [ 1, 2, 3 ]
Arrray.from方法
Array.from 可以接受 Set 结构的参数,转化为了一个数组。
const myset = new Set([1, 2, 3]);const arr = Array.from(myset);console.info("arr", arr); // arr [ 1, 2, 3 ]
应用
了解了 Set 数据的特性和 API 方法,可以再深入了解 Set 常用的场景。
数组,字符串等数据的去重
如上所述,因为 Set 结构的值是唯一的,所以对于元素值重复情况不确定的数组,可以通过转 Set 结构,再转回数组的方式来实现去重。
const array = [1, 1, 2, 3, 3];const newArray = [...new Set(array)];console.info(newArray);
同样的,先展开字符串,添加到 Set 结构,再拼接回来,也可以去除重复字符,得到新的不带重复字符的字符串。
const str = "aaaadcc";const newString = [...new Set(str)].join("");console.info(newString); // adc
同样的,凡是需要不能存在重复元素的情况,都可以使用 Set 结构做一层中转以去重。
实现并集交集和差集
阮一峰老师的 Set 实现并集·交集·差集这部分内容,将 Set 结构的特性 和 API 的使用完美配合。 上代码:
let a = new Set([1, 2, 3]);let b = new Set([4, 3, 2]);// 并集let union = new Set([...a, ...b]);// Set {1, 2, 3, 4}// 交集let intersect = new Set([...a].filter(x => b.has(x)));// set {2, 3}// (a 相对于 b 的)差集let difference = new Set([...a].filter(x => !b.has(x)));// Set {1}
将两个 Set 结构展开,放到同一个数组再传给 new Set(),重复元素被去除,得到并集。
其中一个 Set 结构 a 转数组,调用filter方法得到 a 中 存在 b 中也存在的元素,返回在两个 Set 中都存在的元素,得到交集。
其中一个 Set 结构 a 转数组,调用filter方法得到在 a 中存在,b 中不存在的元素,将 a 中相对于 b 重复的元素去除,得到差集。
