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

更新日期: 2025-10-23 阅读: 56 标签: es6

在日常编程中,我们经常需要处理数据集合。虽然数组(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://fly63.com/article/detial/13044

es6 箭头函数的使用总结,带你深入理解js中的箭头函数

箭头函数是ES6中非常重要的性特性。它最显著的作用就是:更简短的函数,并且不绑定this,arguments等属性,它的this永远指向其上下文的 this。它最适合用于非方法函数,并且它们不能用作构造函数。

详解JavaScript模块化开发require.js

js模块化的开发并不是随心所欲的,为了便于他人的使用和交流,需要遵循一定的规范。目前,通行的js模块规范主要有两种:CommonJS和AMD

js解构赋值,关于es6中的解构赋值的用途总结

ES6中添加了一个新属性解构,允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。用途:交换变量的值、从函数返回多个值、函数参数的定义、提取JSON数据、函数参数的默认值...

ES6中let变量的特点,使用let声明总汇

ES6中let变量的特点:1.let声明变量存在块级作用域,2.let不能先使用再声明3.暂时性死区,在代码块内使用let命令声明变量之前,该变量都是不可用的,4.不允许重复声明

ES6的7个实用技巧

ES6的7个实用技巧包括:1交换元素,2 调试,3 单条语句,4 数组拼接,5 制作副本,6 命名参数,7 Async/Await结合数组解构

ES6 Decorator_js中的装饰器函数

ES6装饰器(Decorator)是一个函数,用来修改类的行为 在设计阶段可以对类和属性进行注释和修改。从本质上上讲,装饰器的最大作用是修改预定义好的逻辑,或者给各种结构添加一些元数据。

基于ES6的tinyJquery

Query作为曾经Web前端的必备利器,随着MVVM框架的兴起,如今已稍显没落。用ES6写了一个基于class简化版的jQuery,包含基础DOM操作,支持链式操作...

ES6 中的一些技巧,使你的代码更清晰,更简短,更易读!

ES6 中的一些技巧:模版字符串、块级作用域、Let、Const、块级作用域函数问题、扩展运算符、函数默认参数、解构、对象字面量和简明参数、动态属性名称、箭头函数、for … of 循环、数字字面量。

Rest/Spread 属性_探索 ES2018 和 ES2019

Rest/Spread 属性:rest操作符在对象解构中的使用。目前,该操作符仅适用于数组解构和参数定义。spread操作符在对象字面量中的使用。目前,这个操作符只能在数组字面量和函数以及方法调用中使用。

使用ES6让你的React代码提升到一个新档次

ES6使您的代码更具表现力和可读性。而且它与React完美配合!现在您已了解更多基础知识:现在是时候将你的ES6技能提升到一个新的水平!嵌套props解构、 传下所有props、props解构、作为参数的函数、列表解构

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!