百度搜索不到网站,做任务赚q红包的网站,个人网站的设计与制作论文,网站表格布局从手写实现到工程实战#xff1a;深入理解 ES6 中的 Set 与 Map 你有没有遇到过这样的场景#xff1f; 想给一个数组去重#xff0c;写了好几行 filter indexOf #xff0c;结果发现对象还去不掉#xff1b; 想用某个 DOM 节点当“键”来存一些临时数据#xff0c…从手写实现到工程实战深入理解 ES6 中的 Set 与 Map你有没有遇到过这样的场景想给一个数组去重写了好几行filterindexOf结果发现对象还去不掉想用某个 DOM 节点当“键”来存一些临时数据却发现 JavaScript 对象的键只能是字符串在做函数缓存时为了处理多个参数绞尽脑汁拼接 key最后还得担心命名冲突……这些问题在 ES6 出现之前几乎是每个前端开发者都踩过的坑。而今天我们要聊的主角 ——Set和Map正是为了解决这些痛点而生。它们不是花架子而是真正改变了我们组织和操作数据的方式。更重要的是理解它们的工作原理不仅能让你写出更高效的代码还能在面试中从容应对“手写一个 Set”这类高频题。为什么我们需要 Set数组去重真的那么简单吗我们先来看一个看似简单的问题如何对数组去重const arr [1, 2, 3, 2, 1]; // 方法一使用 filter indexOf const unique1 arr.filter((item, index) arr.indexOf(item) index); // 方法二使用 Set一行解决 const unique2 [...new Set(arr)];两种写法都能得到[1, 2, 3]但性能差距巨大方法时间复杂度filter indexOfO(n²)new Set()O(n)别小看这个差异。当数组长度达到 10000 级别时前者可能卡顿几百毫秒后者几乎无感。但这还不是全部问题。如果数组里存的是对象呢const users [ { id: 1, name: Alice }, { id: 2, name: Bob }, { id: 1, name: Alice } // 重复项 ]; // 你会发现无论 indexOf 还是 includes 都无法识别这两个对象相等 users.includes(users[0]); // true users.includes({ id: 1, name: Alice }); // false因为对象比较的是引用地址内容相同也没用。这个时候Set依然束手无策 —— 它也只能基于引用去重。但我们可以通过封装逻辑配合Map来解决这类需求后文会讲。那 Set 到底是怎么工作的你可以把Set想象成一个“智能盒子”它只允许每个值进去一次。它的底层通常基于哈希表实现插入、查找、删除平均时间复杂度都是O(1)。虽然 V8 引擎内部实现细节很复杂但从行为上我们可以这样抽象添加元素时先检查是否已存在如果不存在则添加并记录顺序所有操作保持插入顺序可遍历。而且Set对NaN友好 —— 尽管NaN ! NaN但在Set中只会保留一个NaN。动手实现一个简易版 Set光说不练假把式。下面我们来手写一个简化但功能完整的MySet类帮助你彻底搞懂它的运行机制。class MySet { constructor(iterable) { this._items []; if (iterable) { for (const item of iterable) { this.add(item); } } } add(value) { if (!this.has(value)) { this._items.push(value); } return this; // 支持链式调用 } has(value) { // 特殊处理 NaN利用 NaN ! NaN 的特性 if (value ! value) { return this._items.some(v v ! v); } return this._items.includes(value); } delete(value) { const index this._items.findIndex(v { // 同样处理 NaN if (v ! v value ! v) return true; return v value; }); if (index -1) { this._items.splice(index, 1); return true; } return false; } clear() { this._items []; } get size() { return this._items.length; } [Symbol.iterator]() { let index 0; return { next: () { if (index this._items.length) { return { value: this._items[index], done: false }; } return { done: true }; } }; } }关键设计点解析has中的value ! value是判断NaN的经典技巧因为只有NaN满足x ! x所以可以用它来做特殊匹配。迭代器协议[Symbol.iterator]让实例支持for...of这是 ES6 可迭代协议的核心让自定义集合也能被原生语法消费。返回this实现链式调用如set.add(1).add(2)提升 API 使用体验。使用数组存储_items是为了教学清晰实际引擎不会这么做includes是 O(n)生产环境多用哈希结构优化。尽管这个版本性能不如原生Set但它完整还原了语义逻辑非常适合学习和调试。MapJavaScript 终于有了真正的字典如果说Set解决了“唯一性”的问题那Map解决的就是“灵活映射”的问题。在 ES6 之前我们只能用普通对象{}来模拟键值对const cache {}; const key someDOMNode; cache[key] data; // ❌ 出问题了为什么出问题因为所有非字符串键都会被自动转成字符串{}.toString(); // [object Object] [] []; // 于是两个不同的对象可能变成同一个键造成数据覆盖。更糟的是原型链上的属性也可能被误读toString in {} // true即使你没定义这就是所谓的“属性名污染”。而Map彻底打破了这些限制。Map 的核心优势对比维度普通对象 ({})Map键类型仅字符串/Symbol任意类型包括对象、函数插入顺序不保证早期 JS 行为不一保证按插入顺序获取大小需Object.keys().length直接.size是否受原型影响是否动态增删性能可能触发隐藏类失效更稳定正因为这些优点Map成为现代 JS 中管理动态映射关系的首选工具。手写实现 MyMap揭开哈希映射的本质同样地我们通过手写MyMap来理解其内部机制。class MyMap { constructor(iterable) { this._items []; if (iterable) { for (const [key, value] of iterable) { this.set(key, value); } } } set(key, value) { const entry this._findEntry(key); if (entry) { entry.value value; } else { this._items.push({ key, value }); } return this; } get(key) { const entry this._findEntry(key); return entry ? entry.value : undefined; } has(key) { return !!this._findEntry(key); } delete(key) { const index this._items.findIndex(entry this._isKeyEqual(entry.key, key) ); if (index -1) { this._items.splice(index, 1); return true; } return false; } clear() { this._items []; } get size() { return this._items.length; } _findEntry(key) { return this._items.find(entry this._isKeyEqual(entry.key, key)); } _isKeyEqual(a, b) { // 处理 NaN NaN if (a ! a b ! b) return true; return a b; } [Symbol.iterator]() { let index 0; return { next: () { if (index this._items.length) { const { key, value } this._items[index]; return { value: [key, value], done: false }; } return { done: true }; } }; } }设计亮点说明_findEntry抽离查找逻辑避免重复代码_isKeyEqual正确处理NaN比较确保语义一致性迭代器返回[key, value]数组符合规范支持解构js for (const [k, v] of myMap) { console.log(k, v); }支持链式调用API 更友好。虽然查找仍是 O(n)但对于小型缓存或配置映射完全够用。更重要的是它揭示了Map的本质一个支持任意键的有序键值对列表。工程实战中的典型应用场景理论懂了怎么用才是关键。以下是我在真实项目中总结的高价值使用模式。场景一用户选中状态管理Setconst selectedIds new Set(); function toggleSelect(id) { selectedIds.has(id) ? selectedIds.delete(id) : selectedIds.add(id); } toggleSelect(1); // 选中 toggleSelect(2); // 再选中 toggleSelect(1); // 取消自动去重生效 console.log([...selectedIds]); // [2]✅ 优势无需手动维护索引天然防重复。场景二DOM 节点元数据绑定Mapconst nodeStyles new Map(); function attachStyle(node, style) { nodeStyles.set(node, style); } function applyStyles() { for (const [node, style] of nodeStyles) { Object.assign(node.style, style); } } // 即使节点被复用也能精准恢复样式 attachStyle(document.getElementById(btn), { color: red });✅ 优势安全附加数据不影响 DOM 结构也不会泄漏全局变量。场景三函数记忆化Memoization优化性能function memoize(fn) { const cache new Map(); return function(...args) { const key args.length 1 ? args[0] : JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result fn.apply(this, args); cache.set(key, result); return result; }; } const fib memoize(n n 1 ? n : fib(n - 1) fib(n - 2)); fib(40); // 原本递归爆炸现在瞬间完成⚠️ 注意对于对象参数建议升级为WeakMap避免内存泄漏。高阶技巧与最佳实践1. 什么时候该用SetvsArray元素需唯一 →Set需频繁判断是否存在 →Set.has()比arr.includes()快得多不关心顺序或需要排序 → 仍可用Array2.Mapvs 普通对象怎么选使用场景推荐选择键是字符串且结构固定{}键是变量/对象/函数Map频繁增删键值对Map需要.size或迭代顺序Map3. 内存安全善用WeakSet和WeakMap如果你用对象作为键并希望在对象被回收时自动释放关联数据请使用弱引用版本const seenNodes new WeakSet(); function processNode(node) { if (seenNodes.has(node)) return; seenNodes.add(node); // 处理逻辑... }WeakSet和WeakMap不会阻止垃圾回收适合做标记、缓存、监听等场景。4. 不要试图JSON.stringify(Set)直接序列化会失败JSON.stringify(new Set([1,2,3])); // {}正确做法JSON.stringify([...new Set([1,2,3])]); // [1,2,3]或者转换为对象形式存储。写在最后掌握 Set 与 Map才算真正迈入现代 JS 开发Set和Map看似只是两个新数据结构实则代表了一种思维方式的转变从“将就用对象模拟”到“选用合适的数据模型”。当你开始思考“这个场景该用Set还是Map”、“要不要考虑内存回收”——你就已经脱离了初级编码阶段。它们也是许多高级特性的基石。比如React中用Set管理依赖列表Vue的响应式系统用WeakMap存储观测关系缓存池、事件总线、权限校验等通用模块广泛采用Map未来 ECMAScript 提案中的HashSets、HashTables、甚至Tuple和Record也都延续了这一理念。所以下次你在写去重逻辑时不要再写filter indexOf了。打开控制台敲下[...new Set(array)]那一瞬间的清爽感就是技术进步带来的最小却最真实的快乐。如果你在实际项目中有有趣的Set/Map用法欢迎在评论区分享交流创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考