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

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

JavaScript ES6 引入的四种数据结构:Set、WeakSet、Map 和 WeakMap。Set 用于存储唯一值,支持去重和集合运算;WeakSet 仅存对象且为弱引用,不阻止垃圾回收。Map 是键值对集合,支持任意类型键并保持插入顺序;WeakMap 仅以对象为键,适合存储私有数据或元数据而不影响对象生命周期。文章对比了它们与普通对象的区别,提供了常用 API、遍历方法及典型应用场景,帮助开发者根据需求选择合适的数据结构。

清心发布于 2026/3/30更新于 2026/7/2957 浏览

一、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();
// 从可迭代对象初始化(如数组)。
const s2 = new Set([1, 2, 3, 2, 1]); // Set(3) {1, 2, 3}
// 字符串会被拆分为字符。
const s3 = 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' }
];
const uniqueUsers = Array.from(
    new Set(users.map(u => u.id)),
    id => users.find(u => u.id === id)
);
// 或使用 Map 更高效。
// 上述使用 Set,时间复杂度为 O(n^2),而使用下述 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 是唯一引用,也可能暂时未被回收。

不能用于原始值。

// 常见错误:
const ids = new WeakSet();
ids.add(123); // TypeError: Invalid value used in weak set

无法遍历 = 无法知道内容。

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()。
存储私有数据且不阻止 GC。WeakMap。

总结

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 是唯一引用,也可能暂时未被回收。

不能用于原始值作键。

// 常见错误:
const wm = new WeakMap();
wm.set('id-123', userData); // TypeError

无法知道 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

更多推荐文章

查看全部
  • Awesome GitHub Copilot 自定义功能介绍
  • ClawX 跨平台安装与配置指南:Windows/macOS/Linux 零基础教程
  • 深入理解 Transformer 架构:从注意力机制到位置编码
  • Jetson Orin NX + Fast-LIO2 自主无人机完整部署方案
  • 2025 CCF CSP-J1 入门级 C++ 试题及答案解析
  • WSL 中 VS Code Remote 连接 Copilot 代理配置问题及解决
  • 前缀和技巧实战:和为 K 的子数组与和可被 K 整除的子数组
  • LeetCode 92 链表区间反转:递归反转与哨兵技巧
  • Windows 系统多版本 JDK 配置与快速切换方法
  • 本地部署 AI 服务的安全远程访问方案:零公网 IP 暴露
  • Ubuntu 22.04 部署 OpenClaw AI 助手实战指南
  • 数据结构——排序算法:冒泡、快速排序与归并排序详解
  • 手机端运行 Stable Diffusion 的开源方案与使用指南
  • 2025 年 12 月 AI 写作工具小说创作能力横评:8 款主流产品实测
  • 阿里云 DashScope、OpenAI 及 aiohttp Python SDK 异步性能对比
  • 黑客与程序员的本质区别及核心技能树解析
  • Python GIS 脚本编程:CARTO、Mapbox 与 Hadoop 实战
  • GitHub Copilot 插件模型选项缺失排查
  • OSS 权限控制实战:ACL、RAM、Bucket Policy 与错误排查
  • 基于 ROS2 与 EKF 的四轮差速机器人里程计精度优化:解决建图漂移与重影问题

相关免费在线工具

  • 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

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online