在 ES6(ES2015)中,JavaScript 引入了两个非常实用的内置数据结构:MapSet。它们弥补了传统对象(Object)和数组(Array)在某些场景下的不足,提供了更高效、更语义化、更安全的数据存储与操作方式。

本文将全面讲解 MapSet 的核心特性、使用方法、对比优势,并结合真实开发场景给出最佳实践建议。


一、为什么需要 Map 和 Set?

❌ 传统方式的痛点

1. 用普通对象 {} 做键值对存储:
  • 键只能是字符串或 Symbol,不能是对象、数字等;
  • 原型链污染风险obj.toString 可能是继承来的,而非你设置的值;
  • 无法直接获取长度(需 Object.keys(obj).length);
  • 无迭代顺序保证(虽然现代 JS 已规范,但语义不清)。
2. 用数组 [] 去重或集合操作:
  • 去重要手动实现(如 filter + indexOf),效率低;
  • 判断元素是否存在需遍历O(n)),而 Set 是 O(1)

MapSet 正是为解决这些问题而生!


二、Map:真正的键值对集合

🔑 核心特性

  • 键可以是任意类型:字符串、数字、对象、函数、甚至 NaN
  • 插入顺序保留:遍历时按插入顺序返回;
  • 内置 size 属性map.size 直接获取元素数量;
  • 无原型污染:不会继承 Object.prototype 的属性。

🧩 基本用法

// 创建 Map
const map = new Map();

// 添加键值对
map.set('name', 'Alice');
map.set(42, 'answer');
map.set({ id: 1 }, 'user object'); // 键是对象!

// 获取值
console.log(map.get('name')); // 'Alice'
console.log(map.get(42));     // 'answer'

// 检查是否存在
console.log(map.has('name')); // true

// 删除
map.delete('name');

// 清空
map.clear();

// 获取大小
console.log(map.size); // 动态更新

🔁 遍历 Map

const map = new Map([
  ['a', 1],
  ['b', 2]
]);

// 遍历键值对(推荐)
for (const [key, value] of map) {
  console.log(key, value);
}

// 其他迭代器
map.keys();   // 迭代所有键
map.values(); // 迭代所有值
map.entries(); // 默认迭代器,等同于 for...of

🔄 与 Object 对比

特性ObjectMap
键类型仅字符串/Symbol任意类型
插入顺序ES2015+ 保证(但语义弱)明确保证
sizeObject.keys().length.size 直接获取
序列化JSON.stringify 支持不支持(需自定义)
性能小数据快大量数据增删更快

💡 建议

  • 当键是运行时动态生成非字符串 → 用 Map
  • 当数据结构是固定字段的记录(如用户信息)→ 用 Object

三、Set:唯一的值集合

🔑 核心特性

  • 自动去重:添加重复值会被忽略;
  • 值可以是任意类型
  • 插入顺序保留
  • O(1) 时间复杂度判断存在性(比数组 indexOf 快得多)。

🧩 基本用法

// 创建 Set
const set = new Set();

// 添加值
set.add(1);
set.add('hello');
set.add({}); // 对象引用不同即视为不同
set.add(1); // 重复,被忽略

console.log(set.size); // 3

// 检查是否存在
console.log(set.has(1)); // true

// 删除
set.delete('hello');

// 清空
set.clear();

🔁 遍历 Set

const set = new Set([1, 2, 3]);

for (const item of set) {
  console.log(item);
}

// 转为数组
const arr = [...set];
// 或
const arr2 = Array.from(set);

✂️ 实用技巧:数组去重

// 最简洁的去重方式(ES6+)
const unique = [...new Set([1, 2, 2, 3, 1])]; // [1, 2, 3]

// 支持对象去重?不行!因为 {a:1} !== {a:1}
// 如需对象去重,需自定义逻辑(如基于 id)

🆚 与 Array 对比

场景推荐
需要频繁判断元素是否存在Sethas() 是 O(1))
需要保持插入顺序且去重Set
需要索引访问(如 arr[0]Array
存储对象并按 ID 去重Map(ID 作 key)

四、高级用法与实战场景

✅ 场景1:缓存计算结果(Memoization)

const cache = new Map();

function expensiveCalc(n) {
  if (cache.has(n)) return cache.get(n);
  
  const result = /* 复杂计算 */;
  cache.set(n, result);
  return result;
}

✅ 比用对象更安全(避免 __proto__ 等键冲突)。


✅ 场景2:统计元素出现次数

const count = (arr) => 
  arr.reduce((map, val) => {
    map.set(val, (map.get(val) || 0) + 1);
    return map;
  }, new Map());

const freq = count(['a', 'b', 'a']); // Map { 'a' => 2, 'b' => 1 }

✅ 场景3:实现双向映射(BiMap)

class BiMap {
  constructor() {
    this.forward = new Map();
    this.reverse = new Map();
  }
  
  set(key, value) {
    this.forward.set(key, value);
    this.reverse.set(value, key);
  }
  
  getKey(value) {
    return this.reverse.get(value);
  }
  
  getValue(key) {
    return this.forward.get(key);
  }
}

✅ 场景4:去重对象数组(基于某个字段)

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alice Updated' }
];

// 基于 id 去重
const uniqueUsers = [
  ...new Map(users.map(user => [user.id, user])).values()
];
// 结果:保留第一个 id=1 的用户

🔥 技巧:Map 的键会自动去重,values() 提取唯一对象。


五、最佳实践与注意事项

✅ 推荐使用场景

数据结构适用场景
Map- 键不是字符串
- 需要频繁增删键值对
- 键值对数量动态变化大
Set- 需要去重
- 需要快速判断元素是否存在
- 存储唯一标识(如已选标签 ID)

⚠️ 注意事项

  1. 对象作为键时,比较的是引用

    const map = new Map();
    map.set({}, 'value1');
    map.set({}, 'value2'); // 不同对象,两个条目!
    
  2. NaN 在 Set/Map 中被视为相等

    new Set([NaN, NaN]).size; // 1 ✅ 符合 IEEE 754
    
  3. MapSet 不能直接 JSON 序列化

    JSON.stringify(new Map()); // "{}"
    // 需自定义序列化:
    JSON.stringify([...map]); // 转为数组再序列化
    
  4. WeakMap / WeakSet 更适合 DOM 引用或私有数据(避免内存泄漏),但本文不展开。


六、性能对比(简要)

  • 小数据量(< 100):Object / Array 可能略快(引擎优化);
  • 大数据量或频繁操作Map / Set 性能更稳定,尤其是 has()delete() 操作;
  • 内存占用Map / Set 略高,但通常可接受。

📊 实测建议:关键路径上用 console.time 对比,不要过早优化。


七、总结

特性MapSet
用途键值对存储唯一值集合
键/值类型任意任意
去重键自动去重值自动去重
判断存在.has(key).has(value)
获取数量.size.size
遍历顺序插入顺序插入顺序

记住一句话

  • 需要“键 → 值”映射,且键不是字符串? → 用 Map
  • 需要“唯一值”集合? → 用 Set

掌握 MapSet,能让你的代码更高效、安全、语义清晰,是现代 JavaScript 开发的必备技能!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐