ES6 Map/WeakMap/Set/WeakSet 全解指南

发布于:2025-05-01 ⋅ 阅读:(23) ⋅ 点赞:(0)

一、设计思想与核心概念

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是最佳选择

网站公告

今日签到

点亮在社区的每一天
去签到