JavaScript 进阶:在合适场景下用 Set 替代 Array
在日常编程中,我们经常需要处理数据集合。虽然数组(Array)很常用,但在某些情况下,使用 Set 会是更好的选择。Set 是 JavaScript 中的一种集合类型,它提供了一些独特的优势。
Set 和 Array 的主要区别
唯一性处理
数组允许重复的值,而 Set 会自动去重:
// 数组 - 允许重复值
const arr = [1, 2, 2, 3, 3, 3];
console.log(arr); // [1, 2, 2, 3, 3, 3]
// Set - 自动去重
const set = new Set([1, 2, 2, 3, 3, 3]);
console.log([...set]); // [1, 2, 3]性能对比
在处理大量数据时,Set 在查找和添加操作上通常更快:
const largeArray = Array.from({length: 10000}, (_, i) => i);
const largeSet = new Set(largeArray);
// 查找操作对比
console.time('Array includes');
largeArray.includes(9999);
console.timeEnd('Array includes'); // 大约 0.1ms
console.time('Set has');
largeSet.has(9999);
console.timeEnd('Set has'); // 大约 0.01ms
// 添加操作对比
console.time('Array push');
largeArray.push(10000);
console.timeEnd('Array push'); // 大约 0.01ms
console.time('Set add');
largeSet.add(10000);
console.timeEnd('Set add'); // 大约 0.005ms常用场景
数组去重
Set 让数组去重变得非常简单:
// 传统去重方法比较麻烦
// 使用 Set 可以一行代码完成
const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3, 4, 5]
// 封装成函数
const removeDuplicates = array => [...new Set(array)];集合运算
Set 很适合进行数学上的集合运算:
// 并集:包含两个集合的所有元素
const union = (setA, setB) => new Set([...setA, ...setB]);
// 交集:只包含两个集合都有的元素
const intersection = (setA, setB) => new Set(
[...setA].filter(x => setB.has(x))
);
// 差集:包含在第一个集合但不在第二个集合的元素
const difference = (setA, setB) => new Set(
[...setA].filter(x => !setB.has(x))
);
// 使用示例
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
console.log([...union(setA, setB)]); // [1, 2, 3, 4]
console.log([...intersection(setA, setB)]); // [2, 3]
console.log([...difference(setA, setB)]); // [1]数据验证
Set 可以方便地检查数据唯一性:
// 验证某个字段的值是否唯一
const validateUnique = (array, field) => {
const values = array.map(item => item[field]);
return values.length === new Set(values).size;
};
const users = [
{ id: 1, email: 'test@test.com' },
{ id: 2, email: 'test@test.com' }, // 重复邮箱
{ id: 3, email: 'other@test.com' }
];
console.log(validateUnique(users, 'email')); // false实际应用示例
标签管理系统
class TagManager {
constructor() {
this.tags = new Set();
}
addTags(newTags) {
newTags.forEach(tag => this.tags.add(tag.toLowerCase()));
}
removeTag(tag) {
this.tags.delete(tag.toLowerCase());
}
hasTag(tag) {
return this.tags.has(tag.toLowerCase());
}
getTags() {
return [...this.tags].sort();
}
mergeWith(otherTagManager) {
return new Set([...this.tags, ...otherTagManager.tags]);
}
}
// 使用示例
const manager = new TagManager();
manager.addTags(['JavaScript', 'Web', 'javascript', 'api']);
console.log(manager.getTags()); // ['api', 'javascript', 'web']权限管理系统
class PermissionManager {
constructor() {
this.userPermissions = new Map(); // 用户ID -> 权限集合
}
grantPermission(userId, permission) {
if (!this.userPermissions.has(userId)) {
this.userPermissions.set(userId, new Set());
}
this.userPermissions.get(userId).add(permission);
}
revokePermission(userId, permission) {
const permissions = this.userPermissions.get(userId);
if (permissions) {
permissions.delete(permission);
}
}
hasPermission(userId, permission) {
const permissions = this.userPermissions.get(userId);
return permissions ? permissions.has(permission) : false;
}
getUserPermissions(userId) {
return [...(this.userPermissions.get(userId) || [])];
}
}
// 使用示例
const pm = new PermissionManager();
pm.grantPermission('user1', 'read');
pm.grantPermission('user1', 'write');
console.log(pm.hasPermission('user1', 'read')); // true简单缓存系统
class SimpleCache {
constructor(maxSize = 100) {
this.cache = new Map();
this.accessOrder = new Set();
this.maxSize = maxSize;
}
set(key, value) {
// 如果缓存已满,移除最久未使用的项
if (this.accessOrder.size >= this.maxSize) {
const firstKey = this.accessOrder.values().next().value;
this.accessOrder.delete(firstKey);
this.cache.delete(firstKey);
}
this.cache.set(key, value);
this.accessOrder.add(key);
}
get(key) {
if (this.cache.has(key)) {
// 更新访问顺序
this.accessOrder.delete(key);
this.accessOrder.add(key);
return this.cache.get(key);
}
return null;
}
}什么时候不适合使用 Set?
虽然 Set 有很多优点,但并不是所有情况都适合使用:
需要保持元素顺序时
虽然 Set 实际按插入顺序迭代,但这不是语言标准保证的。如果需要严格的顺序控制,应该使用数组。需要重复元素时
Set 会自动去重,如果你需要保留重复元素,必须使用数组。需要索引访问时
const arr = ['a', 'b', 'c'];
console.log(arr[1]); // 'b' - 数组支持索引访问
const set = new Set(['a', 'b', 'c']);
// set[1] 这样写是不行的,Set 不支持索引访问需要进行复杂数组操作时
如果你需要频繁使用 map、filter、reduce 等数组方法,使用数组会更方便:const arr = [1, 2, 3, 4, 5];
const doubled = arr.map(x => x * 2); // 很简单
const set = new Set([1, 2, 3, 4, 5]);
const doubledSet = new Set([...set].map(x => x * 2)); // 需要转换选择指南
使用 Set 的情况:
需要确保值的唯一性
频繁检查某个值是否存在
进行集合运算(并集、交集、差集)
需要快速添加和删除元素
使用 Array 的情况:
需要保留重复元素
需要按索引访问元素
需要保持严格的元素顺序
需要频繁使用数组方法(map、filter 等)
需要与其他期望数组的 API 交互
总结
Set 是 JavaScript 中一个很有用的数据结构。在合适的场景下使用 Set,可以提升代码的性能和可读性。关键是了解它的特性,在需要唯一性检查和快速查找时使用 Set,在其他情况下继续使用数组。
最好的做法是根据具体需求选择合适的数据结构。有时候,甚至可以将两者结合使用,发挥各自的优势。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!