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 2
true // has 方法返回
true // delete 方法返回
size 1
Set {} //删除之后空的 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
// BB
for (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 重复的元素去除,得到差集。