JavaScript中Map和Set详解

JavaScript(JS)中的 Map 和 Set,它们都是 ES6 引入的集合类型,用于更灵活地存储和操作数据。


一、Map 详解

  1. Map 是什么?

Map 是键值对集合,类似对象 Object,但它的键可以是任意类型:

· 字符串、数字、布尔、null、undefined

· 对象、数组、函数

· NaN、Symbol 等

并且 Map 会保持插入顺序。

js 复制代码
const map = new Map();

const objKey = { id: 1 };
const funcKey = function () {};

map.set('name', 'Alice');
map.set(1, 'one');
map.set(objKey, '对象键');
map.set(funcKey, '函数键');
map.set(NaN, 'NaN 也可以作为键');

console.log(map.get('name')); // Alice
console.log(map.get(objKey)); // 对象键
console.log(map.get(NaN));    // NaN 也可以作为键

  1. 创建 Map
js 复制代码
// 空 Map
const map1 = new Map();

// 从二维数组创建
const map2 = new Map([
  ['a', 1],
  ['b', 2],
]);

// 从另一个 Map 创建
const map3 = new Map(map2);

// 从对象创建
const map4 = new Map(Object.entries({ a: 1, b: 2 }));

  1. Map 常用 API

方法/属性 说明

map.set(key, value) 添加/修改键值对,返回 Map 本身,可链式调用

map.get(key) 获取值,不存在返回 undefined

map.has(key) 判断是否存在某个键,返回布尔值

map.delete(key) 删除某个键,返回是否删除成功

map.clear() 清空所有键值对

map.size 返回键值对数量,是属性,不是方法

map.keys() 返回键的迭代器

map.values() 返回值的迭代器

map.entries() 返回 key, value 迭代器

map.forEach() 遍历

js 复制代码
const map = new Map();

map.set('a', 1).set('b', 2).set('c', 3);

console.log(map.size);     // 3
console.log(map.get('a')); // 1
console.log(map.has('b')); // true

map.delete('b');
console.log(map.has('b')); // false

map.clear();
console.log(map.size);     // 0

  1. 遍历 Map
js 复制代码
const map = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3],
]);

// for...of,默认遍历 entries
for (const [key, value] of map) {
  console.log(key, value);
}

// 只遍历键
for (const key of map.keys()) {
  console.log(key);
}

// 只遍历值
for (const value of map.values()) {
  console.log(value);
}

// forEach
map.forEach((value, key, map) => {
  console.log(key, value);
});

注意:Map 的 forEach 回调参数顺序是 (value, key, map),和数组的 forEach((item, index)) 不同。


  1. Map 的键比较规则

Map 的键比较使用 SameValueZero 算法:

· NaN 等于 NaN

· +0 和 -0 视为相同

· 对象、数组、函数等按引用比较,不是内容比较

js 复制代码
const map = new Map();

map.set(NaN, 'nan');
console.log(map.get(NaN)); // nan

map.set({}, 'obj1');
console.log(map.get({})); // undefined,因为不是同一个对象引用

const obj = {};
map.set(obj, 'obj2');
console.log(map.get(obj)); // obj2

  1. Map 与 Object 的区别

对比项 Map Object

键类型 任意类型 只能是 string 或 symbol

顺序 严格插入顺序 数字键会排序,字符串键按插入顺序

大小 map.size Object.keys(obj).length

迭代 可直接迭代 需要 Object.keys/values/entries

原型 无原型链干扰 有原型链,可能键冲突

频繁增删 通常更优 一般

JSON 不能直接序列化 可直接 JSON.stringify


  1. Map 转换
js 复制代码
const map = new Map([
  ['a', 1],
  ['b', 2],
]);

// Map 转数组
console.log([...map]); // [['a', 1], ['b', 2]]
console.log([...map.keys()]);   // ['a', 'b']
console.log([...map.values()]); // [1, 2]

// Map 转对象
const obj = Object.fromEntries(map);
console.log(obj); // { a: 1, b: 2 }

// 对象转 Map
const map2 = new Map(Object.entries(obj));

// JSON 序列化
console.log(JSON.stringify([...map])); // [["a",1],["b",2]]

  1. Map 使用场景

· 需要非字符串键,比如对象、函数作为键

· 需要频繁增删键值对

· 需要严格保持插入顺序

· 需要方便获取大小 size

· 缓存、频率统计、数据关联

频率统计示例:

js 复制代码
const arr = ['a', 'b', 'a', 'c', 'b', 'a'];
const countMap = new Map();

for (const item of arr) {
  countMap.set(item, (countMap.get(item) || 0) + 1);
}

console.log(countMap); // Map(3) { 'a' => 3, 'b' => 2, 'c' => 1 }

二、Set 详解

  1. Set 是什么?

Set 是值的集合,其中的值唯一,不能重复。值可以是任意类型,并且保持插入顺序。

js 复制代码
const set = new Set();

set.add(1);
set.add(2);
set.add(2); // 重复,不会添加
set.add('hello');
set.add({ a: 1 });

console.log(set); // Set(4) { 1, 2, 'hello', { a: 1 } }

  1. 创建 Set
js 复制代码
// 空 Set
const set1 = new Set();

// 从数组创建
const set2 = new Set([1, 2, 2, 3]);
console.log(set2); // Set(3) { 1, 2, 3 }

// 从字符串创建
const set3 = new Set('hello');
console.log(set3); // Set(4) { 'h', 'e', 'l', 'o' }

// 从另一个 Set 创建
const set4 = new Set(set2);

  1. Set 常用 API

方法/属性 说明

set.add(value) 添加值,返回 Set 本身,可链式调用

set.has(value) 判断是否存在,返回布尔值

set.delete(value) 删除值,返回是否删除成功

set.clear() 清空

set.size 返回元素数量,是属性

set.keys() 返回值的迭代器

set.values() 返回值的迭代器,和 keys() 一样

set.entries() 返回 value, value 迭代器

set.forEach() 遍历

js 复制代码
const set = new Set();

set.add(1).add(2).add(3);

console.log(set.size);     // 3
console.log(set.has(2));   // true

set.delete(2);
console.log(set.has(2));   // false

set.clear();
console.log(set.size);     // 0

  1. 遍历 Set
js 复制代码
const set = new Set(['a', 'b', 'c']);

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

// keys() 和 values() 一样
for (const value of set.values()) {
  console.log(value);
}

// entries() 返回 [value, value]
for (const [v1, v2] of set.entries()) {
  console.log(v1, v2);
}

// forEach
set.forEach((value, value2, set) => {
  console.log(value, value2);
});

注意:Set 的 forEach 回调参数是 (value, value, set),第一个和第二个参数都是当前值。


  1. Set 的唯一性规则

Set 也使用 SameValueZero 比较:

· NaN 只存一个

· +0 和 -0 视为相同

· 对象按引用比较

js 复制代码
const set = new Set();

set.add(NaN);
set.add(NaN);
console.log(set.size); // 1

set.add(+0);
set.add(-0);
console.log(set.size); // 2,因为前面已有 NaN,+0 和 -0 只算一个

const obj1 = {};
const obj2 = {};
set.add(obj1);
set.add(obj2);
console.log(set.size); // 4,两个不同对象引用

  1. Set 与 Array 的区别

对比项 Set Array

唯一性 值唯一 可重复

索引 无索引 有索引

查找 has 通常 O(1) includes 通常 O(n)

顺序 插入顺序 插入顺序

方法 较少 丰富,如 map/filter/reduce

去重 天然去重 需手动去重


  1. Set 转换
js 复制代码
const set = new Set([1, 2, 3]);

// Set 转数组
const arr1 = [...set];        // [1, 2, 3]
const arr2 = Array.from(set); // [1, 2, 3]

// 数组转 Set
const set2 = new Set([1, 2, 2, 3]); // Set(3) { 1, 2, 3 }

// 数组去重
const unique = [...new Set([1, 2, 2, 3, 3, 4])];
console.log(unique); // [1, 2, 3, 4]

// JSON 序列化
console.log(JSON.stringify([...set])); // [1,2,3]

  1. Set 使用场景

· 数组去重

· 判断元素是否存在

· 集合运算:并集、交集、差集、对称差集

· 记录已访问、已选中的元素

集合运算示例:

js 复制代码
const a = new Set([1, 2, 3]);
const b = new Set([3, 4, 5]);

// 并集
const union = new Set([...a, ...b]);
console.log(union); // Set(5) { 1, 2, 3, 4, 5 }

// 交集
const intersection = new Set([...a].filter(x => b.has(x)));
console.log(intersection); // Set(1) { 3 }

// 差集:a 有 b 没有
const difference = new Set([...a].filter(x => !b.has(x)));
console.log(difference); // Set(2) { 1, 2 }

// 对称差集:只在一个集合中出现
const symmetricDifference = new Set([
  ...[...a].filter(x => !b.has(x)),
  ...[...b].filter(x => !a.has(x)),
]);
console.log(symmetricDifference); // Set(4) { 1, 2, 4, 5 }

三、WeakMap 和 WeakSet

  1. WeakMap

WeakMap 是键值对集合,但:

· 键必须是对象

· 键是弱引用

· 不能遍历,没有 size、clear、keys、values、entries、forEach

· 只有 set、get、has、delete

js 复制代码
const wm = new WeakMap();

const obj = {};
wm.set(obj, '一些数据');

console.log(wm.get(obj)); // 一些数据
console.log(wm.has(obj)); // true

wm.delete(obj);
console.log(wm.has(obj)); // false

特点:当 obj 没有其他引用时,可以被垃圾回收,WeakMap 中的对应数据也会自动释放,避免内存泄漏。

用途:

· 给 DOM 节点关联数据

· 给对象添加私有数据

· 缓存对象相关数据


  1. WeakSet

WeakSet 是值的集合,但:

· 值必须是对象

· 值是弱引用

· 不能遍历,没有 size、clear、keys、values、entries、forEach

· 只有 add、has、delete

js 复制代码
const ws = new WeakSet();

const obj = {};
ws.add(obj);

console.log(ws.has(obj)); // true
ws.delete(obj);
console.log(ws.has(obj)); // false

用途:

· 标记对象是否被处理过

· 避免内存泄漏

· 不想手动管理集合生命周期时


四、对比总结

Map vs Object

特性 Map Object

键类型 任意 string / symbol

顺序 插入顺序 数字键排序,字符串键插入顺序

大小 size 手动计算

迭代 直接可迭代 需要转换

原型干扰 无 有

JSON 不直接支持 支持

Set vs Array

特性 Set Array

唯一性 唯一 可重复

索引 无 有

查找 has O(1) includes O(n)

方法 少 多

去重 天然支持 需手动

Map vs WeakMap

特性 Map WeakMap

键类型 任意 必须是对象

引用 强引用 弱引用

可遍历 是 否

size 有 无

clear 有 无

垃圾回收 不自动释放 键被回收后自动释放

Set vs WeakSet

特性 Set WeakSet

值类型 任意 必须是对象

引用 强引用 弱引用

可遍历 是 否

size 有 无

clear 有 无

垃圾回收 不自动释放 值被回收后自动释放


五、注意事项

  1. Map / Set 不是数组,不能直接使用 map、filter、reduce,需要先转数组。
  2. JSON.stringify 默认不支持 Map / Set:
    · JSON.stringify(new Map()) 得到 {}
    · JSON.stringify(new Set()) 得到 {}
    · 需要手动转数组或对象。
  3. 对象作为键或值时,按引用比较,不是内容比较。
  4. Map / Set 的 size 是属性,不是方法,不要写成 size()。
  5. WeakMap / WeakSet 不可遍历,所以不能用于需要遍历的场景。
  6. WeakMap 的键、WeakSet 的值必须是对象,不能是原始值。
  7. Map 的 forEach 参数是 (value, key, map),Set 的 forEach 参数是 (value, value, set)。

六、一句话总结

· Map:键值对集合,键可以是任意类型,适合频繁增删、非字符串键、保持顺序。

· Set:唯一值集合,适合去重、存在性判断、集合运算。

· WeakMap / WeakSet:弱引用版本,键/值必须是对象,不可遍历,适合避免内存泄漏。

· 需要遍历、大小、清空:用 Map / Set。

· 需要弱引用、自动垃圾回收:用 WeakMap / WeakSet。

相关推荐
daols881 小时前
vue 表格组件 vxe-table 实现自定义多行编辑功能
前端·javascript·vue.js·vxe-table
志尊宝1 小时前
Vue3 零基础每日笔记(075):图片懒加载与骨架屏
javascript·vue.js·笔记
IT_Octopus2 小时前
【零基础入门 LLM 开发 · Day 11】:ChatOpenAI vs init_chat_model——一行换供应商
java·前端·javascript
柚yuzumi2 小时前
React 19 Hooks 入门:函数组件的四大法宝 (useState / useEffect / useRef / useContext)
前端·javascript·架构
King禅2 小时前
JavaScript 调试实战指南:从 console.log 到 DevTools 深度运用
javascript
全栈项目管理程序猿2 小时前
ArcGIS JS 基础教程(28):图层渲染顺序管理
前端·javascript
扶风ff2 小时前
练题簿在线免费刷题:创建个人任务,把刷题、听题和模考目标放到每天的学习中
开发语言·javascript·学习·小程序
asong2 小时前
没有源代码,AI 也能拆解软件:这个开源项目已有 3.7 万 Star
javascript·后端·github
用户1733598075372 小时前
纯前端 PDF 压平避坑指南:压平后表单字段变了?
前端·javascript·vue.js