Set 的基本用法
Set 是 ES6 引入的一种新的数据结构,它类似于数组,但一个关键区别:Set 中的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构。
重点:唯一的!唯一的!唯一的!
// 创建 Set
const mySet = new Set();
// 添加值
mySet.add(1);
mySet.add(2);
mySet.add(2); // 这个不会被添加,因为 2 已经存在
mySet.add('hello');
mySet.add({ name: 'John' });
console.log(mySet.size); // 4
console.log(mySet.has(2)); // true
Set 的基本特性
1. 值的唯一性
const numbers = [1, 2, 2, 3, 4, 4, 5]; // 2 和 4 重复
const uniqueNumbers = new Set(numbers);
console.log(uniqueNumbers); // Set(5) {1, 2, 3, 4, 5}
console.log([...uniqueNumbers]); // [1, 2, 3, 4, 5] - 转回数组
2. 值的相等性判断
Set 使用 “SameValueZero” 算法判断值是否相等,类似于 ===,但有一些区别
const set = new Set();
set.add(1);
set.add('1'); // 可以添加,因为 1 和 '1' 不同类型
set.add(NaN);
set.add(NaN); // NaN 只能存在一个,虽然 NaN !== NaN
console.log(set); // Set(3) {1, '1', NaN}
3. 对象引用的处理(去重)
const set = new Set();
const obj1 = { name: 'Alice' };
const obj2 = { name: 'Alice' }; // 不同的对象引用
const obj3 = obj1; // 相同的对象引用
set.add(obj1);
set.add(obj2); // 会被添加,因为 obj1 !== obj2
set.add(obj3); // 不会被添加,因为 obj1 === obj3
console.log(set.size); // 2
Set 的常用方法
1. 基本操作
// 定义
const set = new Set();
// 添加元素
set.add('apple');
set.add('banana');
set.add('orange');
// 检查是否存在
console.log(set.has('apple')); // true
console.log(set.has('grape')); // false
// 删除元素
set.delete('banana');
// 清空 Set
// set.clear();
// 获取大小
console.log(set.size); // 2
2. 遍历 Set
Set 结构的实例有四个遍历方法,可以用于遍历成员。
Set.prototype.keys():返回 键名 的遍历器Set.prototype.values():返回 键值 的遍历器Set.prototype.entries():返回 键值对 的遍历器Set.prototype.forEach():使用回调函数遍历每个成员
const fruitSet = new Set(['apple', 'banana', 'orange', 'grape']);
// 使用 for...of
for (let fruit of fruitSet) {
console.log(fruit);
}
// 使用 forEach
fruitSet.forEach((value, valueAgain) => {
console.log(value); // Set 的 forEach 回调中,value 和 key 相同
});
// 获取值的迭代器
const values = fruitSet.values();
console.log(values.next().value); // 'apple'
// 获取键的迭代器(Set 中键和值相同)
const keys = fruitSet.keys();
console.log([...keys]); // ['apple', 'banana', 'orange', 'grape']
// 获取键值对迭代器
const entries = fruitSet.entries();
for (let [key, value] of entries) {
console.log(`${key} = ${value}`); // key 和 value 相同
}3. 初始化 Set
// 从数组初始化
const set1 = new Set([1, 2, 3, 4, 4, 5]); // 4 重复
console.log(set1); // Set(5) {1, 2, 3, 4, 5}
// 从字符串初始化(会拆分为字符)
const set2 = new Set('hello');
console.log(set2); // Set(4) {'h', 'e', 'l', 'o'}
// 从可迭代对象初始化
function* numberGenerator() {
yield 1;
yield 2;
yield 2;
yield 3;
}
const set3 = new Set(numberGenerator());
console.log(set3); // Set(3) {1, 2, 3}
实际应用
1. 数组去重
// 简单数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
// 对象数组去重(基于特定属性)
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice' }, // 重复
{ id: 3, name: 'Charlie' }
];
const uniqueUsers = Array.from(
new Map(users.map(user => [user.id, user])).values()
);
console.log(uniqueUsers); // 去重后的用户数组
2. 集合运算
这其实不是 ES6 时提出的,是 ES2025 为 Set 结构添加了以下集合运算方法。
Set.prototype.intersection(other):交集Set.prototype.union(other):并集Set.prototype.difference(other):差集Set.prototype.symmetricDifference(other):对称差集Set.prototype.isSubsetOf(other):判断是否为子集Set.prototype.isSupersetOf(other):判断是否为超集Set.prototype.isDisjointFrom(other):判断是否不相交
class SetOperations {
// 并集
static union(setA, setB) {
return new Set([...setA, ...setB]);
}
// 交集
static intersection(setA, setB) {
return new Set([...setA].filter(x => setB.has(x)));
}
// 差集 (A - B)
static difference(setA, setB) {
return new Set([...setA].filter(x => !setB.has(x)));
}
// 对称差集 (A ∪ B - A ∩ B)
static symmetricDifference(setA, setB) {
return new Set([
...this.difference(setA, setB),
...this.difference(setB, setA)
]);
}
// 子集判断
static isSubset(setA, setB) {
return [...setA].every(x => setB.has(x));
}
}
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
console.log(SetOperations.union(setA, setB)); // Set(6) {1, 2, 3, 4, 5, 6}
console.log(SetOperations.intersection(setA, setB)); // Set(2) {3, 4}
console.log(SetOperations.difference(setA, setB)); // Set(2) {1, 2}
3. 标签系统
class TagManager {
constructor() {
this.tags = new Set();
}
addTag(tag) {
const normalizedTag = tag.toLowerCase().trim();
if (normalizedTag) {
this.tags.add(normalizedTag);
}
}
removeTag(tag) {
this.tags.delete(tag.toLowerCase());
}
hasTag(tag) {
return this.tags.has(tag.toLowerCase());
}
getTags() {
return [...this.tags];
}
// 合并标签
mergeTags(otherTags) {
otherTags.forEach(tag => this.addTag(tag));
}
}
const manager = new TagManager();
manager.addTag('JavaScript');
manager.addTag(' programming ');
manager.addTag('javascript'); // 不会重复添加
console.log(manager.getTags()); // ['javascript', 'programming']
4. 访问控制
class AccessControl {
constructor() {
this.allowedUsers = new Set();
this.blockedUsers = new Set();
}
grantAccess(userId) {
this.allowedUsers.add(userId);
this.blockedUsers.delete(userId); // 如果之前被阻止,现在取消阻止
}
revokeAccess(userId) {
this.allowedUsers.delete(userId);
}
blockUser(userId) {
this.blockedUsers.add(userId);
this.allowedUsers.delete(userId);
}
canAccess(userId) {
return this.allowedUsers.has(userId) && !this.blockedUsers.has(userId);
}
// 批量操作
grantAccessToUsers(userIds) {
userIds.forEach(id => this.grantAccess(id));
}
}
评论