一、设计思想与核心概念
1. 解决传统结构的痛点
- Object:键只能是字符串/Symbol、无序、无size属性
- Array:查找效率低(
O(n)
)、无自动去重机制 - 核心突破:
// 传统方式 vs ES6方式 const obj = { [{}]: 'value' }; // 键会被转为"[object Object]" const map = new Map().set({}, '真实对象键'); // 保留原始键类型
2. 四大金刚特性对比
结构 | 键类型 | 值特性 | 可迭代 | 弱引用 | 典型内存消耗(1w条) |
---|---|---|---|---|---|
Map | 任意类型 | 任意数据 | ✔️ | ❌ | ~2.4MB |
WeakMap | 仅对象 | 任意数据 | ❌ | ✔️ | ~0.7MB |
Set | - | 唯一值 | ✔️ | ❌ | ~1.8MB |
WeakSet | 仅对象 | 唯一对象 | ❌ | ✔️ | ~0.5MB |
二、实例方法与属性详解
1. Map 方法全表
方法/属性 | 语法示例 | 时间复杂度 | 说明 |
---|---|---|---|
size | map.size |
O(1) | 实时条目计数 |
set(key, value) | map.set({id:1}, 'data') |
O(1) | 返回Map本身(可链式调用) |
get(key) | map.get('name') |
O(1) | 未找到返回undefined |
has(key) | map.has(NaN) |
O(1) | 返回布尔值 |
delete(key) | map.delete(key) |
O(1) | 返回操作是否成功 |
clear() | map.clear() |
O(n) | 清空所有条目 |
keys() | for(const k of map.keys()) |
O(n) | 返回键的迭代器 |
values() | map.values() |
O(n) | 返回值的迭代器 |
entries() | map.entries() |
O(n) | 返回[key, value]迭代器(默认) |
forEach(callback) | map.forEach((v,k)=>...) |
O(n) | 遍历方法 |
2. WeakMap 特殊方法
const wm = new WeakMap();
const obj = {};
// 仅三个方法可用
wm.set(obj, 'private'); // ✔️
wm.get(obj); // ✔️
wm.has(obj); // ✔️
wm.delete(obj); // ✔️
// 以下操作均不支持
wm.size; // ❌ undefined
wm.clear(); // ❌ 方法不存在
wm.keys(); // ❌ 不可迭代
3. Set 核心方法
方法/属性 | 示例 | 说明 |
---|---|---|
add(value) | set.add(100).add(200) |
链式调用,自动去重 |
delete(value) | set.delete(NaN) |
返回操作结果 |
has(value) | set.has('test') |
存在性检测 |
entries() | set.entries() |
返回[value, value]迭代器 |
4. WeakSet 方法限制
const ws = new WeakSet();
const obj = {};
ws.add(obj); // ✔️ 仅对象
ws.has(obj); // ✔️
ws.delete(obj); // ✔️
ws.add('str'); // ❌ TypeError
ws.size; // ❌ undefined
三、使用场景与技巧
1. Map 最佳实践
场景1:DOM节点元数据存储
const nodeMap = new Map();
function handleClick(node) {
if (!nodeMap.has(node)) {
nodeMap.set(node, {
clickCount: 0,
lastClick: null
});
}
const meta = nodeMap.get(node);
meta.clickCount++;
meta.lastClick = Date.now();
}
场景2:配置优先级队列
class PriorityQueue {
constructor() {
this.map = new Map([[1, []], [2, []], [3, []]]);
}
add(item, priority) {
this.map.get(priority).push(item);
}
process() {
for (const [level, tasks] of this.map) {
while(tasks.length) {
this.execute(tasks.shift());
}
}
}
}
2. WeakMap 高级用法
实现真正私有属性
const privateStore = new WeakMap();
class BankAccount {
constructor(balance) {
privateStore.set(this, {
balance: balance,
transactions: []
});
}
deposit(amount) {
const data = privateStore.get(this);
data.balance += amount;
data.transactions.push({ type: 'deposit', amount });
}
get balance() {
return privateStore.get(this).balance;
}
}
深度克隆辅助
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj);
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
3. Set 实战技巧
场景:用户权限校验
const ADMIN = Symbol('admin');
const EDITOR = Symbol('editor');
const userPermissions = new Set([ADMIN, EDITOR]);
function canDeleteContent(user) {
return userPermissions.has(ADMIN);
}
// 动态权限管理
function updatePermissions(user, perm) {
userPermissions.add(perm);
}
高效数组去重
// 传统方式 vs Set方式
const arr = [1,2,2,3,3,3];
// O(n^2)
const uniqueArr = arr.filter((v,i) => arr.indexOf(v) === i);
// O(n)
const setWay = [...new Set(arr)];
4. WeakSet 特殊应用
防止循环引用
const seen = new WeakSet();
function safeStringify(obj) {
if (typeof obj === 'object' && obj !== null) {
if (seen.has(obj)) throw new Error('循环引用');
seen.add(obj);
}
// ...序列化处理
}
四、性能优化策略
1. 基准测试对比(V8引擎)
操作 | Map(1e6次) | Object(1e6次) | 差异 |
---|---|---|---|
插入操作 | ~120ms | ~95ms | -17% |
读取操作 | ~65ms | ~80ms | +23% |
删除操作 | ~110ms | ~450ms | +309% |
迭代操作 | ~200ms | ~350ms | +75% |
优化建议:
- 大数据集(>10万条):优先使用Map
- 频繁删除:必须使用Map
- 内存敏感:考虑WeakMap/WeakSet
2. 内存管理技巧
// 错误示范:内存泄漏
const cache = new Map();
function processData(data) {
cache.set(data.id, data);
// 长期持有引用,data无法被GC
}
// 正确做法:WeakMap自动清理
const weakCache = new WeakMap();
function processObj(obj) {
weakCache.set(obj, Date.now());
}
五、对比总结与决策矩阵
1. 四维对比表
特性 | Map | WeakMap | Set | WeakSet |
---|---|---|---|---|
键类型 | Any | Object | - | Object |
值唯一性 | No | No | Yes | Yes |
可序列化 | Yes | No | Yes | No |
垃圾回收 | 强引用 | 弱引用 | 强引用 | 弱引用 |
迭代能力 | Full | None | Full | None |
典型内存占用 | 较高 | 较低 | 中等 | 最低 |
2. 场景决策树
需要键值对存储?
├─ 需要非对象键 → Map
├─ 需要内存优化 → WeakMap
└─ 需要简单标记 → Set/WeakSet
需要保证元素唯一?
├─ 基本类型 → Set
└─ 对象类型 → WeakSet
数据生命周期?
├─ 长期存在 → Map/Set
└─ 临时使用 → WeakMap/WeakSet
3. 综合建议
- 优先考虑Map:当需要键值对且不确定数据结构时
- 内存敏感场景:使用Weak系列,如DOM节点关联数据
- 高性能去重:无脑选择Set替代数组方案
- 私有数据管理:WeakMap是最佳选择