JavaScript 进阶:在合适场景下用 Set 替代 Array

摘要:在日常编程中,我们经常需要处理数据集合。虽然数组(Array)很常用,但在某些情况下,使用 Set 会是更好的选择。Set 是 JavaScript 中的一种集合类型,它提供了一些独特的优势。

在日常编程中,我们经常需要处理数据集合。虽然数组(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,在其他情况下继续使用数组。

最好的做法是根据具体需求选择合适的数据结构。有时候,甚至可以将两者结合使用,发挥各自的优势。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13044