跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端算法

JavaScript 集合 (Set、WeakSet) 与映射 (Map、WeakMap)

JavaScript 集合 (Set、WeakSet) 与映射 (Map、WeakMap) 是 ES6 引入的核心数据结构。Set 存储唯一值,支持去重和集合运算;WeakSet 仅存对象且弱引用,便于生命周期管理。Map 提供任意类型键的键值对映射,保持插入顺序;WeakMap 以对象为键且不可遍历,适合私有数据存储。掌握这些结构能优化性能并避免内存泄漏。

星云发布于 2026/3/24更新于 2026/8/2148 浏览

一、Set 数据类型

JavaScript 中的 Set 是 ES6(ES2015)引入的一种集合数据结构,用于存储唯一值(unique values)的有序列表。无论是原始类型(如数字、字符串)还是对象引用,Set 都会自动去重。

基本特性

特性说明
值唯一不允许重复元素(使用 === 判断相等,但 NaN === NaN 被视为相等)。
有序元素按插入顺序迭代。
可存储任意类型包括 number、string、object、NaN、undefined 等。
非索引结构不能通过下标访问(不像数组),但可遍历。

注意:Set 中的 {} 和 {} 被视为不同对象(因为引用不同),所以不会去重。

详细介绍

1. 常用方法与属性
方法说明示例
add(value)添加元素(返回 Set 自身,可链式调用)。s.add(4)
delete(value)删除元素(返回布尔值)。s.delete(1) → true
has(value)检查是否存在(返回布尔值)。s.has(2) → true
clear()清空所有元素。s.clear()
forEach(callback)遍历元素。s.forEach(v => console.log(v))

属性:size——返回元素个数。

s2.size; // 3

创建 Set。

// 空 Set
const s1 = new Set();
// 从可迭代对象初始化(如数组)。
 s2 =  ([, , , , ]); 

 s3 =  (); 
const
new
Set
1
2
3
2
1
// Set(3) {1, 2, 3}
// 字符串会被拆分为字符。
const
new
Set
'hello'
// Set(4) {'h', 'e', 'l', 'o'}
2. Set 与 Array 互转

Set vs Array

场景推荐
需要去重Set
需要频繁判断元素是否存在Set(has() 时间复杂度 O(1),Array 的 includes() 是 O(n))
需要索引/顺序操作(如 sort, splice)Array
存储大量数据且频繁增删Set 更高效

特殊值处理

const s = new Set();
s.add(NaN);
s.add(NaN);
console.log(s.size); // 1 → NaN 被视为相等
s.add(0);
s.add(-0);
console.log(s.size); // 2 → +0 和 -0 被视为不同(符合 IEEE 754)

Array → Set(自动去重)

const arr = [1, 2, 2, 3];
const set = new Set(arr); // Set {1, 2, 3}

经典去重技巧

const unique = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]

Set → Array

const set = new Set([1, 2, 3]);
const arr = [...set]; // 或 Array.from(set)

遍历 Set

Set 是可迭代对象(iterable),支持以下方式遍历:

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

// 1. for...of
for (const item of set) {
  console.log(item); // 1, 2, 3
}

// 2. forEach
// `Set` 的 `forEach` 回调函数参数是 `(value, value, set)`,没有 key(与 Map 不同)
set.forEach((value, valueAgain, setRef) => {
  console.log(value); // value 和 valueAgain 相同(Set 没有 key)
});

// 3. 扩展运算符转为数组
[...set]; // [1, 2, 3]

// 4. Array.from
Array.from(set); // [1, 2, 3]
3. 高级用法示例

去除对象数组中的重复项(基于某属性)

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

// 使用 Set,时间复杂度为 O(n^2)
const uniqueUsers = Array.from(
  new Set(users.map(u => u.id)),
  id => users.find(u => u.id === id)
);

// 或使用 Map 更高效,时间复杂度为 O(n)
const uniqueUsersMap = Array.from(new Map(users.map(u => [u.id, u])).values());
/* 
users.map(u => [u.id, u]) 生成键值对数组:[[1, obj1], [2, obj2], [1, obj3], ...]。
new Map(...) Map 的 key 自动去重,后出现的相同 key 会覆盖前面的(若想保留第一个,可反向遍历)。
.values() 获取所有唯一对象。
Array.from(...) 转为数组。
*/

求两个数组的并集、交集、差集

const a = [1, 2, 3];
const b = [3, 4, 5];

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

// 交集
const intersection = [...new Set(a.filter(x => b.includes(x)))];

// 差集(a - b)
const diff = a.filter(x => !new Set(b).has(x)); // [1, 2]
4. 注意事项
  • Set不支持直接获取第 n 个元素(无 .get(index) 方法)。
  • Set 无法序列化为 JSON(需先转数组):
    JSON.stringify([...mySet]);
    
  • 对象去重依赖引用相等,不是内容相等:
    new Set([{ a: 1 }, { a: 1 }]).size; // 2(两个不同对象)
    
5. 总结
  • Set 是处理'唯一值集合'的最佳工具,尤其适合去重、成员检测、集合运算等场景。结合扩展运算符和数组方法,能写出简洁高效的代码。
  • 常用口诀:
    • 去重要用 Set。
    • 存在检查用 .has()。
    • 遍历用 for...of 或 forEach。
    • 转数组用 [...set]。

二、WeakSet 数据类型

WeakSet 是 JavaScript(ES6 引入)中一种特殊的集合数据结构,它与 Set 类似,但有关键限制和用途,那就是 WeakSet 只能存储对象(不能是原始值),且对对象的引用是'弱引用'(weakly held)——不会阻止垃圾回收(GC)。

核心特性

特性说明
只能存对象不能添加 number、string、boolean 等原始值。
弱引用存入的对象如果没有其他引用,会被 GC 自动回收。
不可迭代没有 .size、.clear()、.entries()、for...of 等方法。
无顺序不保证元素顺序(也无法遍历)。
私有性无法知道 WeakSet 中有哪些对象(设计如此)。

基本用法

核心方法(只有 3 个!)

方法说明示例
add(value)添加对象。ws.add(obj)
has(value)检查对象是否存在。ws.has(obj) → true/false
delete(value)删除对象。ws.delete(obj)
const obj = {};
ws.add(obj);
console.log(ws.has(obj)); // true
ws.delete(obj);
console.log(ws.has(obj)); // false

没有 size 属性! 你无法知道 WeakSet 里有多少元素。

创建和操作

const ws = new WeakSet();
// 只能添加对象
ws.add({});
ws.add(document.body);
ws.add(new Date()); // 报错:不能添加原始值
ws.add(42); // TypeError
ws.add('hello'); // TypeError
ws.add(true); // TypeError

典型场景

缓存或元数据(生命周期跟随对象)

// 为每个请求对象附加临时元数据
const requestMetadata = new WeakSet();
fetch('/api/data').then(res => {
  requestMetadata.add(res); // ... 处理响应
});
// 当 res 对象被 GC 回收时,元数据自动消失

私有数据存储(不污染对象)

// 模拟私有属性(避免在对象上直接挂 _privateData)
const privateData = new WeakSet();
class User {
  constructor(name) {
    this.name = name;
    privateData.add(this); // 标记该实例有私有数据
  }
  isValid() {
    // 检查是否属于'有效用户'
    return privateData.has(this);
  }
}

标记对象(而不阻止 GC)

// 用于标记'已处理'的 DOM 元素
const processedElements = new WeakSet();
function processElement(el) {
  if (processedElements.has(el)) {
    return; // 已处理过,跳过
  }
  // ... 处理逻辑
  processedElements.add(el);
}
// 当 el 从 DOM 移除且无其他引用时,
// 它会自动从 WeakSet 中消失(无需手动清理)

WeakSet vs Set

特性SetWeakSet
存储类型任意值(对象/原始值)仅对象
引用类型强引用(阻止 GC)弱引用(不阻止 GC)
可遍历是(for...of, .size 等)否(完全不可遍历)
用途通用去重、集合运算生命周期绑定对象的标记/元数据

简单记忆:

  • 用 Set 存'我要主动管理的数据'。
  • 用 WeakSet 存'这个对象有某种状态,但我不负责它的生死'。

重要注意事项

  1. 弱引用 ≠ 立即回收。
    • 对象是否被回收取决于 GC 时机。
    • 即使 WeakSet 是唯一引用,也可能暂时未被回收。
  2. 不能用于原始值。
    // 常见错误
    const ids = new WeakSet();
    ids.add(123); // TypeError: Invalid value used in weak set
    
  3. 无法遍历 = 无法知道内容。
    const ws = new WeakSet();
    ws.add({ a: 1 });
    ws.add({ b: 2 });
    // 你无法列出所有元素!
    // 没有 ws.size, 没有 for...of, 没有 .values()
    
    这是刻意设计:防止开发者依赖内部状态,确保弱引用语义。

高级技巧:结合 FinalizationRegistry(ES2021)

监听对象被 GC 的时机(谨慎使用):

const registry = new FinalizationRegistry((heldValue) => {
  console.log(`${heldValue} 被回收了`);
});
const ws = new WeakSet();
const obj = { id: 1 };
ws.add(obj);
registry.register(obj, 'Object with id=1');
obj = null; // 解除引用
// 未来某刻 GC 后,会输出 "Object with id=1 被回收了"

FinalizationRegistry 是实验性 API,不推荐常规使用。

总结

WeakSet 是一个'只增不查全貌'的对象标记工具,核心价值在于:'我知道这个对象有某种状态,但我不持有它,让它自由生灭。'

使用口诀:

  • 存对象(不能存数字/字符串)。
  • 做标记(如'已初始化'、'已验证')。
  • 免清理(随对象自动消失)。
  • 别遍历(根本做不到)。
  • 别计数(没有 .size)。

三、Map 数据类型

Map 是 JavaScript(ES6/ES2015 引入)中一种键值对集合(key-value collection) 数据结构,用于存储任意类型的键和值的映射关系。它比普通对象({})更强大、更灵活,尤其适合键不是字符串或需要精确控制键值行为的场景。

Map vs 普通对象

特性普通对象 {}Map
键的类型仅字符串/Symbol任意类型(包括对象、函数、数字等)
键的顺序无序(ES2015 后部分有序)插入顺序(严格保持)
大小获取需手动计算(Object.keys(obj).length)直接 .size
迭代需 Object.keys() 等辅助原生可迭代(for...of、.entries() 等)
原型污染风险有(如 obj.__proto__)无(纯净数据结构)
性能大量动态属性时较慢大量数据时更快(专为频繁增删优化)

简单说:Map 是'真正的哈希表',而对象是'为固定结构设计的'。

基本用法

1. 遍历 Map

Map 是可迭代对象,支持多种遍历方式:

转为数组

const keys = [...map.keys()]; // [ 'name', 1, {} ]
const values = [...map.values()]; // [ 'Bob', 'number key', 'object key' ]
const entries = [...map.entries()]; // [ ['name','Bob'], [1,'...'], ... ]

内置迭代器方法

// 所有键
for (const key of map.keys()) { ... }
// 所有值
for (const value of map.values()) { ... }
// 所有键值对(默认)
for (const entry of map.entries()) {
  console.log(entry); // [key, value]
}

for...of(推荐)

for (const [key, value] of map) {
  console.log(key, value);
}
2. 核心方法
方法说明示例
set(key, value)添加/更新键值对。map.set('a', 1)
get(key)获取值(不存在返回 undefined)。map.get('a') → 1
has(key)检查是否存在键。map.has('a') → true
delete(key)删除键值对(返回布尔值)。map.delete('a')
clear()清空所有键值对。map.clear()
size属性:返回元素数量。map.size → 0
const map = new Map();
map.set('name', 'Bob');
map.set(1, 'number key');
map.set({}, 'object key'); // 键可以是对象!
console.log(map.get('name')); // 'Bob'
console.log(map.has(1)); // true
console.log(map.size); // 3

创建 Map

// 空 Map
const map = new Map();
// 从可迭代对象初始化(如数组的数组)
const map2 = new Map([
  ['name', 'Alice'],
  [42, 'answer'],
  [{ id: 1 }, 'user object']
]);

关键特性详解

  • 用 DOM 元素作键 → 存储其状态。
  • 用函数作键 → 缓存计算结果。

保持插入顺序

const map = new Map();
map.set(3, 'three');
map.set(1, 'one');
map.set(2, 'two');
console.log([...map.keys()]); // [3, 1, 2](按插入顺序)

严格相等比较(SameValueZero)

键的比较使用 SameValueZero 算法(类似 ===,但 NaN === NaN 为真)。

const map = new Map();
map.set(NaN, 'not a number');
console.log(map.get(NaN)); // 'not a number'(普通对象做不到!)

键可以是任意类型

const user1 = { id: 1 };
const user2 = { id: 2 };
const userMap = new Map();
userMap.set(user1, 'Alice');
userMap.set(user2, 'Bob');
console.log(userMap.get(user1)); // 'Alice'
// 注意:必须用同一个对象引用!
console.log(userMap.get({ id: 1 })); // undefined(新对象)

适用于:

Map vs WeakMap

特性MapWeakMap
键的类型任意仅对象
可枚举是否(无 size、clear()、entries() 等)
垃圾回收键被强引用(阻止 GC)弱引用(键可被 GC 回收)
用途通用键值存储私有数据、DOM 元素元数据

WeakMap 适合:不希望阻止对象被回收的场景(如给 DOM 元素附加临时数据)。

实用技巧

缓存函数结果(记忆化)

const cache = new Map();
function expensiveFn(arg) {
  if (cache.has(arg)) return cache.get(arg);
  const result = /* 耗时计算 */;
  cache.set(arg, result);
  return result;
}

去重数组(基于对象内容)

// 按 id 去重
const users = [{ id: 1 }, { id: 2 }, { id: 1 }];
const unique = [...new Map(users.map(u => [u.id, u])).values()];

Map 转对象(仅字符串键)

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

对象转 Map

const obj = { a: 1, b: 2 };
const map = new Map(Object.entries(obj));

注意事项

不支持点语法访问

map.key; // undefined(必须用 map.get('key'))

键是对象时,必须用相同引用

map.set({ x: 1 }, 'value');
map.get({ x: 1 }); // undefined(新对象)

不能用 JSON.stringify() 直接序列化

JSON.stringify(new Map()); // '{}'
// 解决方案:先转为数组
JSON.stringify([...map]);

性能建议

场景推荐
键是字符串/数字,结构固定普通对象 {}
键类型多样、动态增删频繁Map
需要与对象互转(仅字符串键)Object.entries()/Object.fromEntries()
存储私有数据且不阻止 GCWeakMap

总结

Map 是现代 JavaScript 中处理键值对的首选数据结构,当你遇到以下情况时,优先考虑它:

  • 键不是字符串(如对象、数字、Symbol)。
  • 需要频繁增删键值对。
  • 需要可靠获取元素数量。
  • 需要保持插入顺序。
  • 担心原型污染。

记住核心 API:

const m = new Map();
m.set(key, value);
m.get(key);
m.has(key);
m.delete(key);
m.size;

四、WeakMap 数据类型

WeakMap 是 JavaScript(ES6 引入)中一种键值对集合(key-value collection),它是 Map 的'弱引用'版本,专为以对象为键、且不希望阻止垃圾回收(GC) 的场景设计。

核心特性

特性说明
键必须是对象不能使用字符串、数字等原始值作键。
值可以是任意类型包括原始值、对象、函数等。
弱引用键键对象如果没有其他引用,会被 GC 自动回收。
不可迭代没有 .size、.clear()、.keys()、for...of 等方法。
私有性无法枚举或查看内部内容(设计如此)。

核心思想:'我想给这个对象附加一些数据,但我不应该影响它的生命周期。'

基本用法

不支持的操作

// 报错:键不能是原始值
wm.set('string', 1); // TypeError
wm.set(42, 'value'); // TypeError
// 不存在的方法
wm.size; // undefined
wm.clear(); // TypeError: wm.clear is not a function
[...wm]; // TypeError: wm is not iterable

创建和操作

const wm = new WeakMap();
// 键必须是对象
const obj1 = {};
const obj2 = { id: 1 };
wm.set(obj1, 'metadata for obj1');
wm.set(obj2, { count: 42 });
console.log(wm.get(obj1)); // 'metadata for obj1'
console.log(wm.has(obj2)); // true
wm.delete(obj1);

典型应用场景

  • 不在对象上挂 _private 属性。
  • 实例销毁时,私有数据自动消失。

缓存计算结果(生命周期跟随对象)

const cache = new WeakMap();
function expensiveCalculation(obj) {
  if (cache.has(obj)) {
    return cache.get(obj);
  }
  const result = /* 耗时计算 */;
  cache.set(obj, result);
  return result;
}
const data = { values: [...] };
expensiveCalculation(data); // 当 data 对象不再被使用,缓存自动释放

DOM 元素元数据缓存

const domMetadata = new WeakMap();
function attachHandler(el) {
  if (domMetadata.has(el)) return; // 避免重复绑定
  const state = { clickCount: 0 };
  domMetadata.set(el, state);
  el.addEventListener('click', () => {
    state.clickCount++;
    console.log('Clicked', state.clickCount, 'times');
  });
}
// 当 el 从 DOM 移除且无其他引用时,
// state 会自动被 GC 回收(无需手动清理!)

私有数据存储(不污染对象本身)

// 模拟类的私有属性(ES2022 前常用技巧)
const privateData = new WeakMap();
class User {
  constructor(name, ssn) {
    this.name = name;
    privateData.set(this, { ssn }); // 私有数据
  }
  getSSN() {
    return privateData.get(this).ssn;
  }
}
const user = new User('Alice', '123-45-6789');
console.log(user.getSSN()); // '123-45-6789'
// 外部无法直接访问 ssn!

优势:

WeakMap vs Map

特性MapWeakMap
键的类型任意类型仅对象
引用类型强引用(阻止 GC)弱引用(不阻止 GC)
可遍历是(.size, for...of 等)否(完全不可遍历)
用途通用键值存储对象关联数据 + 自动内存管理

选择原则:

  • 需要遍历/计数 → 用 Map。
  • 只想'悄悄'给对象附加数据 → 用 WeakMap。

重要注意事项

  1. 弱引用 ≠ 立即回收。
    • 对象是否被回收取决于 GC 时机。
    • 即使 WeakMap 是唯一引用,也可能暂时未被回收。
  2. 不能用于原始值作键。
    // 常见错误
    const wm = new WeakMap();
    wm.set('id-123', userData); // TypeError
    
  3. 无法知道 WeakMap 里有什么。
    const wm = new WeakMap();
    wm.set({}, 'secret');
    // 你无法:
    // - 获取 size
    // - 列出所有键
    // - 检查是否为空
    // 这是刻意设计,确保弱引用语义不被破坏
    

高级技巧:结合 FinalizationRegistry(谨慎使用)

监听对象被 GC 的时机:

const registry = new FinalizationRegistry((heldValue) => {
  console.log(`对象 ${heldValue} 已被回收`);
});
const wm = new WeakMap();
const obj = { id: 1 };
wm.set(obj, 'data');
registry.register(obj, 'Object with id=1');
obj = null; // 解除引用
// 未来 GC 后,会触发回调

FinalizationRegistry 是实验性 API,不推荐常规使用。

总结

WeakMap 是'对象专属的私密笔记本':

  • 你只能通过原对象查笔记。
  • 对象消失了,笔记自动焚毁。
  • 没人能偷看你的笔记本内容.

使用口诀:

  • 键是对象(不能是字符串/数字)。
  • 存私有数据(不污染对象)。
  • 免内存泄漏(随对象自动清理)。
  • 别想遍历(根本做不到)。
  • 别问大小(没有 .size)。

目录

  1. 一、Set 数据类型
  2. 基本特性
  3. 详细介绍
  4. 1. 常用方法与属性
  5. 2. Set 与 Array 互转
  6. 3. 高级用法示例
  7. 4. 注意事项
  8. 5. 总结
  9. 二、WeakSet 数据类型
  10. 核心特性
  11. 基本用法
  12. 典型场景
  13. WeakSet vs Set
  14. 重要注意事项
  15. 高级技巧:结合 FinalizationRegistry(ES2021)
  16. 总结
  17. 三、Map 数据类型
  18. Map vs 普通对象
  19. 基本用法
  20. 1. 遍历 Map
  21. 2. 核心方法
  22. 关键特性详解
  23. Map vs WeakMap
  24. 实用技巧
  25. 注意事项
  26. 性能建议
  27. 总结
  28. 四、WeakMap 数据类型
  29. 核心特性
  30. 基本用法
  31. 典型应用场景
  32. WeakMap vs Map
  33. 重要注意事项
  34. 高级技巧:结合 FinalizationRegistry(谨慎使用)
  35. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ 二叉搜索树原理与增删查实现详解
  • Java 性能优化的 50 个细节
  • OpenAI Python API 使用指南:基础功能与异步处理
  • 数据结构与算法实战:LeetCode 移除元素与合并有序数组
  • 学术写作辅助:智能降重与 AIGC 检测适配指南
  • 从盲行到跑酷:深度视觉如何赋予足式机器人极限运动能力
  • UI UX Pro Max:让 AI 编码助手具备专业前端设计能力
  • Flutter 三方库 shelf_modular 的鸿蒙化适配指南
  • SpringBoot+Vue 语言考试信息报名系统管理平台
  • Sora 是世界模拟器吗?通用世界模型综述解析
  • HTTP 请求方式详解:GET、POST 及其他常用方法
  • SpringBoot 整合 Neo4j 图数据库项目实战
  • 前端防抖节流主流库实战与避坑指南
  • Rust 与 WebAssembly 深度实战:浏览器与 Node.js 的高性能融合
  • VS Code 配置 AI Agent Skills 指南
  • 分布式文件系统 HDFS 的数据读写过程
  • OpenClaw 移动端部署实战:iOS/Android 语音唤醒与离线 AI
  • 直流无刷电机 FOC 控制算法详解与 STM32 实现
  • WhisperLiveKit 实时语音识别指南:从安装到生产部署
  • AIGC 技术解析:定义、核心特点与应用场景

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online