ES6的Set数据结构:从数组去重到高效数据管理
1. 为什么你需要了解Set数据结构第一次遇到数组去重问题时我像大多数新手一样写了这样的代码function unique(arr) { let result []; for (let i 0; i arr.length; i) { if (result.indexOf(arr[i]) -1) { result.push(arr[i]); } } return result; }直到有一天项目里有个10万条数据的数组需要去重这段代码直接让页面卡死了5秒。这就是我转向Set数据结构的转折点。用Set改造后function unique(arr) { return [...new Set(arr)]; }同样的数据量执行时间从5秒降到了50毫秒性能提升了100倍这就是ES6的Set数据结构给我的震撼教育。Set本质上是一种集合类型它最大的特点就是所有元素都是唯一的。想象你有一个装彩色球的盒子Set就像是个智能盒子当你试图放入两个颜色相同的球时它会自动过滤掉重复的球。这种特性让它在处理唯一性数据时特别高效。与数组相比Set有几个显著优势查找速度更快判断元素是否存在的时间复杂度是O(1)而数组的indexOf是O(n)自动去重添加重复元素时不会报错只是静默忽略更语义化当你要处理的数据本身就是数学上的集合概念时用Set比用数组更符合直觉2. Set的基本操作从创建到增删改查2.1 创建和初始化Set创建Set有三种常见方式// 1. 创建空Set const emptySet new Set(); // 2. 用数组初始化 const numSet new Set([1, 2, 3, 4]); // 3. 用字符串初始化会自动拆分为字符 const charSet new Set(hello); console.log(charSet); // Set(4) {h, e, l, o}实际项目中我经常用第三种方式快速获取字符串中的唯一字符。比如统计用户输入的所有不重复标签const tags 科技,互联网,人工智能,科技,AI; const uniqueTags [...new Set(tags.split(,))]; console.log(uniqueTags); // [科技, 互联网, 人工智能, AI]2.2 增删查操作Set的API设计得非常简洁主要方法就四个const mySet new Set(); // 添加元素 mySet.add(1).add(2).add(3); // 可以链式调用 // 检查存在 console.log(mySet.has(2)); // true // 删除元素 mySet.delete(2); console.log(mySet.has(2)); // false // 清空集合 mySet.clear(); console.log(mySet.size); // 0这里有个实用技巧因为add()方法返回Set本身所以可以链式调用。这在批量添加数据时特别方便const set new Set() .add(苹果) .add(香蕉) .add(橙子);2.3 大小和类型判断Set的size属性可以获取元素数量这比数组的length更准确因为它已经自动去重了const dupArr [1, 2, 2, 3, 3, 3]; console.log(dupArr.length); // 6 console.log(new Set(dupArr).size); // 3关于类型判断有个特殊点需要注意在Set中NaN被视为相同的值const set new Set(); set.add(NaN); set.add(NaN); console.log(set.size); // 1这是Set为数不多的例外情况因为在JavaScript中NaN NaN的结果是false但Set内部做了特殊处理。3. Set的高级应用不只是数组去重3.1 复杂数据结构的去重方案虽然Set不能直接处理对象去重但配合JSON.stringify可以解决简单对象去重问题const objArr [ {id: 1, name: 张三}, {id: 2, name: 李四}, {id: 1, name: 张三} ]; const uniqueObjArr [ ...new Map( objArr.map(obj [JSON.stringify(obj), obj]) ).values() ];对于更复杂的场景比如要根据某个属性去重可以这样处理const users [ {id: 1, name: Alice}, {id: 2, name: Bob}, {id: 1, name: Alice} ]; const uniqueUsers [ ...new Map(users.map(user [user.id, user])).values() ];3.2 集合运算的实现利用Set可以轻松实现数学上的集合运算// 并集 function union(setA, setB) { return new Set([...setA, ...setB]); } // 交集 function intersection(setA, setB) { return new Set([...setA].filter(x setB.has(x))); } // 差集 function difference(setA, setB) { return new Set([...setA].filter(x !setB.has(x))); }这些方法在处理标签系统、权限系统时特别有用。比如获取用户新增的权限const oldPermissions new Set([read, write]); const newPermissions new Set([read, delete]); const added difference(newPermissions, oldPermissions); console.log(added); // Set(1) {delete}3.3 性能敏感场景的应用在需要频繁检查元素是否存在的场景Set的性能优势非常明显。比如实现一个敏感词过滤系统const sensitiveWords new Set([暴力, 色情, 政治]); function containsSensitiveWord(text) { return text.split().some(word sensitiveWords.has(word)); }根据我的测试当敏感词库达到10万条时Set方案的性能仍然是数组方案的100倍以上。4. Set的遍历与转换4.1 多种遍历方式对比Set提供了四种遍历方法各有适用场景const colorSet new Set([red, green, blue]); // 1. for...of直接遍历 for (const color of colorSet) { console.log(color); } // 2. forEach方法 colorSet.forEach((value, _valueAgain, set) { console.log(value); }); // 3. 获取迭代器 const iterator colorSet.values(); console.log(iterator.next().value); // red // 4. 转换为数组后遍历 [...colorSet].map(color console.log(color));需要注意的是Set的forEach回调函数有三个参数前两个都是当前元素的值这是为了保持与Map的API一致性。4.2 与其他数据结构的转换Set与数组的转换是最常见的操作// Set转数组 const set new Set([1, 2, 3]); const arr1 [...set]; const arr2 Array.from(set); // 数组转Set const newSet new Set([1, 2, 3]);更复杂的转换场景包括// Set转Map const setToMap new Map([...mySet].map(item [item, item])); // Map转Set const mapToSet new Set(myMap.values());4.3 实际案例实现一个投票系统假设我们要实现一个防止重复投票的系统class VotingSystem { constructor() { this.voters new Set(); this.votes new Map(); } vote(voterId, candidate) { if (this.voters.has(voterId)) { console.log(您已经投过票了); return false; } this.voters.add(voterId); this.votes.set(candidate, (this.votes.get(candidate) || 0) 1); return true; } getResults() { return [...this.votes.entries()]; } }这个实现利用了Set来高效检查重复投票比用数组方案要高效得多。