B07 Map 与 Set 集合类型

2026-10-31 17:130 阅读约 13 分钟JavaScript

B07 Map 与 Set 集合类型

摘要:对象的键只能是字符串与 Symbol,且会继承原型链上的属性;Map 允许任意值作为键并保持插入顺序,Set 保证值唯一。WeakMap 与 WeakSet 的键是弱引用,不阻止回收,适合给对象挂载私有数据。本文给出四类集合的增删改查、迭代顺序、与对象的取舍对照,以及 WeakMap 缓存与私有状态两个典型用法。
关键词:Map,Set,WeakMap,弱引用,迭代顺序

所属:frontend/js / B-进阶 | 上一篇:B06-错误处理与调试 | 下一篇:B08-正则表达式 | 返回:B00-总结

1 引言

用对象当字典时会遇到三类问题:键 1 与 "1" 冲突、遍历顺序不保证、键名与原型属性(toString、constructor)可能撞车。用对象数组做去重则要写嵌套循环。

本文给出四类集合类型的选择依据。范围见表 1-1。

项 本文覆盖 本文不覆盖
键值 Map 增删改查、迭代顺序、链式 自定义哈希结构
唯一值 Set 去重、集合运算 不可变集合库
弱引用 WeakMap/WeakSet 用途与限制 垃圾回收算法(C03)
取舍 四类集合与对象的对照 JSON 序列化(C06)

表 1-1 本文覆盖范围

2 预备知识

项 要求 核对方式
环境 Node.js 20+ 或 Chrome 130+ 能执行 ESM 脚本
前置 A06、C03 数组与可达性已掌握
文件 js-practice/collections2.mjs 编码 UTF-8

表 2-1 环境与前置要求

3 原理与实现

3.1 四类集合的边界

按「键的类型」与「是否弱引用」划分,如图 3-1 所示。

flowchart TD
    A["需要键值对"] --> B{"键是否必须为对象"}
    B -->|是| C{"是否需要遍历"}
    C -->|否| D["WeakMap<br/>键弱引用,不可遍历"]
    C -->|是| E["Map 以对象为键"]
    B -->|否| F["Map"]
    G["只需要值的唯一性"] --> H{"值是否为对象且不需遍历"}
    H -->|是| I["WeakSet"]
    H -->|否| J["Set"]

图 3-1 四类集合按键类型与遍历需求划分

图上可见:弱引用集合不可遍历、没有 size,这是回收器能随时回收键的代价;需要遍历就必须用 Map/Set。

3.2 完整实现

// js-practice/collections2.mjs;运行环境:Node.js 20+
// 1 Map:任意键,保持插入顺序
const counts = new Map();
const keyObj = { id: 1 };

counts.set('a', 1);
counts.set(2, 'two');
counts.set(keyObj, '对象键');

console.log(counts.get(keyObj));        // 对象键
console.log(counts.get('2'));           // undefined,2 与 '2' 是不同键
console.log([...counts.keys()]);        // ['a', 2, { id: 1 }]
console.log(counts.size, counts.has('a')); // 3 true

// 2 Map 与对象的取舍:对象做字典会撞原型属性
const dict = Object.create(null);       // 无原型,避免 toString 冲突
dict.toString = 'ok';
console.log(dict.toString);             // ok

// 3 Set:去重与集合运算
const ids = [3, 1, 2, 1, 3];
const unique = [...new Set(ids)];
console.log(unique);                    // [3, 1, 2]

const online = new Set([1, 2, 3]);
const banned = new Set([3]);
const active = [...online].filter((id) => !banned.has(id));   // 差集
console.log(active);                    // [1, 2]
console.log(online.difference?.(banned) ?? '当前运行时不支持 difference');

// 4 WeakMap:给对象挂私有数据,键被回收时条目自动消失
const privateData = new WeakMap();

class User {
  constructor(name) {
    privateData.set(this, { name, loginAt: Date.now() });
  }
  get name() {
    return privateData.get(this).name;
  }
}

const u = new User('Lin');
console.log(u.name, Object.keys(u));    // Lin [],外部看不到内部数据

// 5 WeakMap 做对象级缓存
const cache = new WeakMap();
function heavy(obj) {
  if (cache.has(obj)) return cache.get(obj);
  const result = Object.keys(obj).length;
  cache.set(obj, result);
  return result;
}

const payload = { a: 1, b: 2 };
console.log(heavy(payload), heavy(payload)); // 2 2
console.log(cache.size);                 // undefined,弱引用集合无 size

四类集合与对象的对照见表 3-1。

需求 首选 原因
键为字符串,结构固定 对象 字面量简洁,可直接解构
键为任意值或需要保序 Map 键不转字符串,插入顺序稳定
需要频繁增删键值对 Map 有 size,遍历无需 Object.keys
值去重 Set has 为常数时间,数组为线性
给对象挂数据且不应泄漏 WeakMap 键被回收时条目自动移除
标记对象是否已处理 WeakSet 不阻止被标记对象被回收

表 3-1 集合类型的选择依据

常用方法见表 3-2。

操作 Map Set WeakMap
增 set(key, value) 可链式 add(value) set(key, value)
查 get(key) has(value) get(key)
删 delete(key) delete(value) delete(key)
清空 clear() clear() 无
数量 size size 无
遍历 keys/values/entries values/entries 不可遍历
可序列化 需先转数组(C06) 需先转数组 否

表 3-2 三类集合的方法对照

4 验证与讨论

  1. 执行脚本,确认 counts.get('2') 为 undefined,说明 Map 的键不转字符串。
  2. 把 dict 改为 {} 后再读取 dict.toString,确认拿到原型上的函数而非字符串。
  3. 在控制台执行 [...new Set([3,1,2,1,3])],确认顺序保留首次出现次序。
  4. 把 privateData 改为 Map,确认实例被丢弃后条目仍存在,堆快照中可见滞留。
检查项 预期结果 说明
键类型 对象键与字符串键共存 表 3-1
插入顺序 遍历顺序与写入一致 3.2 节
去重 重复值只保留一个 Set
私有性 Object.keys(u) 为空 WeakMap 不在实例上留属性
无 size cache.size 为 undefined 弱引用集合不可统计

表 4-1 五项验收

讨论与局限。 Set 的唯一性基于 SameValueZero 比较,NaN 等于自身,+0 与 -0 视为同一个值,对象按引用比较。另,difference、union 等集合运算方法在较旧运行时不可用,需手写 filter 实现。

5 常见问题

现象 原因 处理
map.get('2') 取不到值 写入时用了数字 2 统一键的类型
用对象当字典时多了属性 原型链上的同名属性 Object.create(null) 或改用 Map
JSON.stringify(map) 得到 {} Map 不可直接序列化 先 [...map] 转数组,见 C06
缓存导致内存不降 用 Map 做对象级缓存 改用 WeakMap
遍历 WeakMap 报错 弱引用集合不支持遍历 需要遍历时改用 Map
去重后顺序变了 用了排序或多轮 filter Set 保留首次出现顺序

表 5-1 集合类型的六类问题

6 结论

序号 结论 依据
1 键为任意值或需保序时用 Map 表 3-1
2 值去重与存在性判断用 Set 表 3-1
3 给对象挂私有数据用 WeakMap 图 3-1
4 弱引用集合不可遍历、无 size 表 3-2

表 6-1 结论汇总

参考文献

[1] MDN Web Docs · Map,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map(访问于 2026-10-31)
[2] ECMA-262 · 24.3 WeakMap Objects,https://tc39.es/ecma262/#sec-weakmap-objects(访问于 2026-10-31)

返回文章列表

评论 0

0/2000

还没有评论,快来抢沙发吧~