JavaScript Set 对象:处理唯一值的利器
在日常 JavaScript 开发中,我们经常需要处理一组数据,并且这组数据里不能有重复项。比如,收集用户标签、统计独立访客 ID,或者过滤掉数组里的重复值。以前我都是用对象来模拟,或者用数组配合 indexOf 或 includes 做去重,但代码写起来总是觉得不够直接。直到 ES6 带来了 Set 对象,这种场景处理起来就顺手多了。
Set 就是一个专门用来存储唯一值的。无论是数字、字符串,还是对象引用,它都能放,但会确保每个值只出现一次。如果你往 Set 里重复添加相同的值,它只会保留第一个。
什么是 Set?
简单来说,Set 是一种数据结构,它内部维护着一组唯一的元素。当你需要确保数据不重复时,它就是很合适的选择。
语法
new Set([iterable])
-
iterable:可选参数。如果传入一个可迭代对象(比如数组),这个对象里的所有元素会被添加到新建的Set中。重复的值会自动被忽略。
Set 的几个关键特点
在使用中,我发现下面这几点是理解 Set 的关键:
-
值的唯一性:这是
Set最根本的特性。它内部会用一种严格相等(===)的逻辑来判断值是否重复。NaN在Set中被视为相等的,即使NaN === NaN的结果是false。 -
有序性:
Set会按照元素被添加的顺序来存储它们。当你遍历Set时,输出的顺序就是插入的顺序。 -
键与值相同:
Set内部实现其实用了“键”的概念,但每个键的值就是键本身。所以你看到entries()方法返回的是[value, value]这样的形式,这在刚开始接触时可能会觉得有点特别,但理解了它的设计意图后就能明白。
Set 的创建与使用
下面我们通过一些例子,在“代码号”学习场景里看看 Set 具体怎么用。
1. 创建 Set
创建一个空的 Set,或者用数组来初始化。
// 代码号学习笔记:创建Set
let emptySet = new Set();
console.log(emptySet); // 输出: Set(0) {}
// 用数组初始化,重复的值会被自动过滤
let fruitSet = new Set(['苹果', '香蕉', '苹果', '橙子', '香蕉']);
console.log(fruitSet);
// 输出: Set(3) { '苹果', '香蕉', '橙子' }
2. 添加元素:add() 方法
用 add() 往 Set 里添加元素。如果添加一个已经存在的值,什么也不会发生。
// 代码号学习笔记:add() 添加元素
let userIds = new Set();
userIds.add(1001);
userIds.add(1002);
userIds.add(1001); // 重复添加,无效
console.log(userIds); // 输出: Set(2) { 1001, 1002 }
console.log(userIds.size); // 输出: 2,size属性可以获取元素个数
3. 检查元素是否存在:has() 方法
在操作之前,经常需要先确认某个值是否已经在里了。has() 方法就是用来做这个的。
// 代码号学习笔记:has() 检查元素是否存在
let activeUsers = new Set(['alice', 'bob', 'carol']);
if (activeUsers.has('alice')) {
console.log('alice 是活跃用户');
}
// 输出: alice 是活跃用户
console.log(activeUsers.has('dave')); // 输出: false
4. 删除元素:delete() 方法
从 Set 中移除一个元素,用 delete()。它会返回一个布尔值,告诉你是否删除成功。
// 代码号学习笔记:delete() 删除元素
let tags = new Set(['前端', 'JavaScript', '教程']);
let removed = tags.delete('JavaScript');
console.log(removed); // 输出: true
console.log(tags); // 输出: Set(2) { '前端', '教程' }
// 删除一个不存在的元素
removed = tags.delete('后端');
console.log(removed); // 输出: false
5. 清空 Set:clear() 方法
如果想一次性清空所有元素,clear() 是最直接的方式。
// 代码号学习笔记:clear() 清空
let tempSet = new Set([1, 2, 3]);
console.log(tempSet.size); // 输出: 3
tempSet.clear();
console.log(tempSet.size); // 输出: 0
6. 遍历 Set:values()、keys()、entries() 与 forEach()
Set 提供了多种遍历方式。值得一提的是,keys() 和 values() 在 Set 中的行为是一样的,都返回值的迭代器。
// 代码号学习笔记:遍历Set
let scores = new Set([88, 92, 75, 100]);
// 使用 values() 或 keys() 遍历
console.log('--- 遍历值 ---');
for (let score of scores.values()) {
console.log(score);
}
// 输出: 88, 92, 75, 100
// 使用 entries(),每个元素返回 [value, value]
console.log('--- entries() 遍历 ---');
for (let entry of scores.entries()) {
console.log(entry);
}
// 输出: [88, 88], [92, 92], [75, 75], [100, 100]
// 使用 forEach
console.log('--- forEach 遍历 ---');
scores.forEach((value, key) => {
// 注意,这里的 key 和 value 是相同的
console.log(`${key}: ${value}`);
});
// 输出: 88: 88, 92: 92, 75: 75, 100: 100
实用场景:用 Set 给数组去重
在开发中,我经常用 Set 来处理数组去重,这是它最实用的场景之一。以前需要写循环或者用 filter,现在一行代码就能搞定。
// 代码号学习笔记:Set 给数组去重
let originalArray = [1, 2, 2, 3, 4, 4, 5, 1];
let uniqueArray = [...new Set(originalArray)];
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
经验之谈:Set 与数组的取舍
有同学可能会问,数组也能存储数据,为什么还要用 Set?我个人的经验是,两者的侧重点不同:
-
如果你关心的是“值是否重复”,并且需要频繁检查某个值是否存在,那
Set更合适。Set的has()方法在性能上通常比数组的includes()要好,尤其是数据量较大的时候。Set内部实现的数据结构(类似哈希表)让查找效率更高。 -
如果你关心的是“顺序”和“索引”,并且需要对元素进行排序、过滤、映射等操作,那数组更合适。数组有丰富的
map、filter、reduce方法,而Set在这方面就比较基础。 -
如果你需要存储一组唯一值,但又需要用到数组的方法,通常的做法是先转成数组处理完再转回
Set。我平时经常这样用,既保证了唯一性,又不丢失数组操作的灵活性。
本节课程知识要点
-
值的唯一性:
Set内部使用严格相等(===)来判断重复,NaN在Set中被视为相等,这是一个需要注意的细节。 -
插入顺序:
Set的迭代顺序就是元素被添加的顺序,这在进行数据展示或需要保持原始顺序时很有用。 -
size属性:获取Set中元素的数量,直接使用set.size即可,比数组的length更直观,也比对象的Object.keys().length更高效。 -
键值同构:
Set的keys()和values()方法返回相同的迭代器,entries()返回[value, value]的键值对,这是为了保持与Map接口的一致性。 -
应用场景:数组去重、管理不重复的 ID 、标签系统、数据过滤等场景,都是
Set比较合适的使用场景。