ES6 Set数据结构:从数组去重到高效数据管理的实战指南

ES6 Set数据结构:从数组去重到高效数据管理的实战指南 1. 为什么你需要了解ES6 Set数据结构第一次遇到数组去重问题时我像大多数新手一样写了这样的代码function unique(arr) { const result []; for (let i 0; i arr.length; i) { if (result.indexOf(arr[i]) -1) { result.push(arr[i]); } } return result; }直到有一天同事告诉我用Set一行代码就能搞定。当时我的反应是这是什么黑魔法 这就是Set给我的第一印象——简单到不可思议却又强大得令人惊讶。Set是ES6引入的一种新数据结构它最直观的特点就是所有元素都是唯一的。你可以把它想象成一个特殊的数组但比数组在某些场景下更高效。比如检查某个值是否存在Set的has()方法比数组的indexOf()快得多特别是在数据量大的时候。在实际项目中我发现Set特别适合这些场景需要快速去重的数据集合需要频繁检查元素是否存在的场景需要存储唯一值的场景如用户ID、商品SKU等2. Set基础从创建到基本操作2.1 创建和初始化Set创建一个空Set很简单const mySet new Set();但更常见的是用数组初始化Setconst numbers [1, 2, 2, 3, 4, 4, 5]; const uniqueNumbers new Set(numbers); console.log(uniqueNumbers); // Set(5) {1, 2, 3, 4, 5}这里有个细节需要注意Set判断值是否相等的算法类似于但有个例外——NaN。在Set中NaN被视为相同的值const nanSet new Set([NaN, NaN]); console.log(nanSet.size); // 12.2 Set的四大基本操作Set提供了四个核心方法我习惯把它们称为增删查清add()- 添加元素const set new Set(); set.add(1).add(2).add(3); // 可以链式调用delete()- 删除元素set.delete(2); // 返回布尔值表示是否删除成功has()- 检查元素是否存在console.log(set.has(1)); // true console.log(set.has(4)); // falseclear()- 清空所有元素set.clear(); console.log(set.size); // 02.3 Set与数组的转换虽然Set很好用但有时我们还是需要数组。转换方法有两种扩展运算符const arr [...new Set([1, 2, 2, 3])];Array.fromconst arr Array.from(new Set([1, 2, 2, 3]));我更喜欢第一种因为它更简洁。但在某些需要兼容旧浏览器的项目中第二种可能更安全。3. Set的高级应用场景3.1 不只是数组去重很多人只知道Set可以用来数组去重其实它的应用场景远不止于此。比如文本分析统计一篇文章中使用了多少个不同的单词const text hello world hello javascript; const words text.split( ); const uniqueWords new Set(words); console.log(uniqueWords.size); // 3用户行为追踪记录用户访问过的页面避免重复记录const visitedPages new Set(); function trackPageVisit(page) { if (!visitedPages.has(page)) { visitedPages.add(page); // 记录访问逻辑... } }3.2 集合运算Set原生支持一些集合运算虽然不如数学中的集合那么全面但足够应付日常开发并集const setA new Set([1, 2, 3]); const setB new Set([3, 4, 5]); const union new Set([...setA, ...setB]);交集const intersection new Set([...setA].filter(x setB.has(x)));差集A有B没有const difference new Set([...setA].filter(x !setB.has(x)));3.3 性能优化在处理大数据量时Set的性能优势非常明显。我曾经做过一个测试const bigArray Array.from({length: 1000000}, (_, i) i); const bigSet new Set(bigArray); // 测试查找性能 console.time(Array includes); bigArray.includes(999999); console.timeEnd(Array includes); // ~5ms console.time(Set has); bigSet.has(999999); console.timeEnd(Set has); // ~0.01ms结果Set的has()方法比数组的includes()快了几百倍这是因为Set底层使用了哈希表实现查找操作的时间复杂度是O(1)而数组是O(n)。4. Set的遍历与迭代4.1 四种遍历方式Set提供了四种遍历方法我刚开始用时经常搞混后来发现其实很简单keys()/values()Set的键和值是一样的const set new Set([a, b, c]); for (const item of set.keys()) { console.log(item); // a, b, c }entries()返回[key, value]对key和value相同for (const [key, value] of set.entries()) { console.log(key, value); // a a, b b, c c }forEach()类似数组的forEachset.forEach((value, key) { console.log(${key}: ${value}); });4.2 实际应用案例案例1数据过滤const validIds new Set([123, 456, 789]); const userInput [123, 999, 456]; const filtered userInput.filter(id validIds.has(id)); console.log(filtered); // [123, 456]案例2统计唯一访客const visitors new Set(); function addVisitor(userId) { visitors.add(userId); } function getUniqueVisitorCount() { return visitors.size; }5. Set的局限性与解决方案5.1 不能直接存储对象Set使用严格相等()来判断元素是否相同所以对象总是唯一的const objSet new Set(); objSet.add({a: 1}); objSet.add({a: 1}); console.log(objSet.size); // 2解决方案是使用唯一标识符const users [ {id: 1, name: Alice}, {id: 1, name: Alice}, {id: 2, name: Bob} ]; const uniqueUsers new Set(users.map(user user.id)); console.log([...uniqueUsers]); // [1, 2]5.2 无法直接序列化JSON.stringify()会忽略Setconst set new Set([1, 2, 3]); console.log(JSON.stringify(set)); // {}解决方案是先转为数组const serialized JSON.stringify([...set]); console.log(serialized); // [1,2,3]5.3 没有直接的过滤/映射方法Set没有类似数组的filter()或map()方法。需要先转为数组const numberSet new Set([1, 2, 3, 4, 5]); const evenNumbers new Set( [...numberSet].filter(x x % 2 0) );6. 实战用Set优化你的代码6.1 替代数组去重传统方式function unique(arr) { return arr.filter((item, index) arr.indexOf(item) index); }Set方式function unique(arr) { return [...new Set(arr)]; }不仅代码更简洁性能也更好特别是大数据量时。6.2 实现简单的权限系统const userPermissions new Set([read, write]); function checkPermission(permission) { return userPermissions.has(permission); } // 添加权限 userPermissions.add(delete); // 删除权限 userPermissions.delete(write);6.3 跟踪已加载的资源在前端开发中避免重复加载资源很重要const loadedScripts new Set(); function loadScript(url) { if (!loadedScripts.has(url)) { loadedScripts.add(url); const script document.createElement(script); script.src url; document.head.appendChild(script); } }7. Set与其他数据结构的比较7.1 Set vs Array特性SetArray元素唯一性✅❌查找性能O(1)O(n)有序性插入顺序索引顺序直接序列化❌✅7.2 Set vs Object很多人用Object来模拟Set的功能const fakeSet {}; fakeSet[key] true; // 添加元素 if (fakeSet[key]) {} // 检查元素但Set更专业键名不受限于字符串有专门的API(add/delete/has)自带size属性更清晰的语义8. 常见问题与解决方案8.1 为什么Set的forEach参数顺序是(value, key)这与Map保持一致虽然Set的key和value相同。这种设计保持了API的一致性。8.2 如何获取Set中的第N个元素Set没有直接索引访问需要先转为数组const set new Set([1, 2, 3]); const secondElement [...set][1]; // 28.3 Set的性能陷阱虽然Set的has()很快但创建Set本身有开销。对于小数据量(如10个元素)直接使用数组可能更快。9. 实际项目中的最佳实践命名约定我习惯在变量名后加Set如userIdsSet提高可读性内存管理大Set用完及时clear()避免内存泄漏类型安全TypeScript中明确泛型类型如new Set()不可变操作如果需要不可变更新创建新Set而不是修改原Set// TypeScript示例 const ids: Setstring new Set(); ids.add(123); // 类型检查10. 进阶WeakSet的使用场景WeakSet是Set的变体区别在于只能存储对象对象是弱引用不影响垃圾回收不可遍历典型用途标记对象而不阻止其被回收const weakSet new WeakSet(); const obj {}; weakSet.add(obj); // 检查对象是否被标记 if (weakSet.has(obj)) { // 执行特定逻辑 }在实际项目中WeakSet常用于实现自定义的对象生命周期管理比如跟踪DOM元素是否被处理过而不用担心内存泄漏。