JavaScript数据存储三剑客:Object、Map与WeakMap完全指南

目录

  • 一、Object
  • 二、Map
  • 三、WeakMap
  • 四、区别比较
    • [4.1 基础操作比较](#4.1 基础操作比较)
    • [4.2 性能对比示例](#4.2 性能对比示例)
    • [4.3 完整区别对比代码](#4.3 完整区别对比代码)
  • 五、总结
    • [5.1 选择建议](#5.1 选择建议)
    • [5.2 核心区别对比](#5.2 核心区别对比)

一、Object

作用:

  • 最基础的键值对存储结构,键只能是字符串或 Symbol。

特点:

  • 键必须是字符串或 Symbol

  • 有原型链,包含默认属性

  • 键值对数量需要手动计算

  • 性能稳定,适合存储结构化数据

业务场景:

javascript 复制代码
// 场景1:实体对象/DTO
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

// 场景2:配置对象
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retryCount: 3
};

// 场景3:方法集合
const utils = {
  formatDate(date) {
    return date.toLocaleDateString();
  },
  parseJSON(str) {
    try {
      return JSON.parse(str);
    } catch {
      return null;
    }
  }
};

二、Map

作用:

  • 完善的键值对集合,键可以是任意类型。

特点:

  • 键可以是任意类型(对象、函数、原始值)

  • 保持插入顺序

  • 有 size 属性,方便获取大小

  • 性能优于 Object(频繁增删场景)

业务场景:

javascript 复制代码
// 场景1:缓存系统
const cache = new Map();

function fetchData(key) {
  if (cache.has(key)) {
    console.log('从缓存获取');
    return cache.get(key);
  }
  
  const data = `数据-${key}`; // 模拟获取数据
  cache.set(key, data);
  
  // 设置过期时间(5秒)
  setTimeout(() => {
    cache.delete(key);
    console.log(`缓存 ${key} 已过期`);
  }, 5000);
  
  return data;
}

// 场景2:DOM节点关联数据
const elementData = new Map();
const button = document.querySelector('#btn');

elementData.set(button, {
  clickCount: 0,
  lastClickTime: null
});

button.addEventListener('click', () => {
  const data = elementData.get(button);
  data.clickCount++;
  data.lastClickTime = new Date();
});

// 场景3:对象键的场景
const objKey1 = { id: 1 };
const objKey2 = { id: 2 };
const map = new Map();
map.set(objKey1, '值1');
map.set(objKey2, '值2');
console.log(map.get(objKey1)); // '值1'

三、WeakMap

作用:

  • 弱引用的 Map,键必须是对象,不会阻止垃圾回收。

特点:

  • 键只能是对象

  • 弱引用,不阻止垃圾回收

  • 不可迭代,没有 size 属性

  • 自动处理内存管理

业务场景:

javascript 复制代码
// 场景1:私有数据存储
const privateData = new WeakMap();

class Person {
  constructor(name, age) {
    privateData.set(this, {
      name,
      age,
      _secret: '私有信息'
    });
  }
  
  getName() {
    return privateData.get(this).name;
  }
  
  getSecret() {
    return privateData.get(this)._secret;
  }
}

const person = new Person('张三', 25);
console.log(person.getName()); // '张三'
// 无法直接访问私有数据
console.log(person._secret); // undefined

// 场景2:DOM节点元数据(无需手动清理)
const domMetadata = new WeakMap();

function trackElement(element, metadata) {
  domMetadata.set(element, metadata);
}

// 当元素被移除时,关联的metadata会自动被垃圾回收
const div = document.createElement('div');
trackElement(div, { created: Date.now(), visits: 0 });

// 场景3:缓存计算结果(避免内存泄漏)
const computedCache = new WeakMap();

function processObject(obj) {
  if (!computedCache.has(obj)) {
    const result = { /* 复杂计算 */ };
    computedCache.set(obj, result);
  }
  return computedCache.get(obj);
}

// 当obj不再被使用时,缓存会自动释放

四、区别比较

4.1 基础操作比较

javascript 复制代码
// 1. 键类型的区别
const obj = {};
obj[{}] = '对象键'; // 实际上键会被转为 '[object Object]'
obj[function(){}] = '函数键'; // 转为字符串

const map = new Map();
map.set({}, '对象键'); // 正常
map.set(() => {}, '函数键'); // 正常

const weakMap = new WeakMap();
// weakMap.set('string', '值'); // 错误!键必须是对象
weakMap.set({}, '对象键'); // 正常

// 2. 迭代和大小获取
const userObj = { name: '张三', age: 25 };
console.log(Object.keys(userObj).length); // 2
console.log(Object.entries(userObj)); // [['name','张三'],['age',25]]

const userMap = new Map([['name','张三'], ['age',25]]);
console.log(userMap.size); // 2
userMap.forEach((value, key) => console.log(key, value));

// WeakMap 无法迭代
const userWeakMap = new WeakMap();
const key = { id: 1 };
userWeakMap.set(key, { name: '张三' });
// console.log(userWeakMap.size); // undefined
// userWeakMap.forEach // 不存在这个方法

4.2 性能对比示例

javascript 复制代码
// 性能测试:频繁增删操作
console.time('Object');
const obj = {};
for (let i = 0; i < 100000; i++) {
  obj[`key${i}`] = i;
}
for (let i = 0; i < 100000; i++) {
  delete obj[`key${i}`];
}
console.timeEnd('Object');

console.time('Map');
const map = new Map();
for (let i = 0; i < 100000; i++) {
  map.set(`key${i}`, i);
}
for (let i = 0; i < 100000; i++) {
  map.delete(`key${i}`);
}
console.timeEnd('Map');

// 内存测试:WeakMap 自动回收
let obj1 = { data: new Array(1000000).fill('*') };
let obj2 = { data: new Array(1000000).fill('*') };

const map2 = new Map();
map2.set(obj1, '元数据1');

const weakMap2 = new WeakMap();
weakMap2.set(obj2, '元数据2');

obj1 = null; // map2 仍然持有引用,内存不会释放
obj2 = null; // weakMap2 的引用会被垃圾回收

// 稍后检查内存使用情况
setTimeout(() => {
  console.log('Map size:', map2.size); // 1
  // WeakMap 无法检查 size
}, 5000);

4.3 完整区别对比代码

javascript 复制代码
// 创建一个详细的对比示例
function compareStructures() {
  // 1. 键类型对比
  console.group('键类型对比');
  
  const obj = {};
  const map = new Map();
  const weakMap = new WeakMap();
  
  const keyObj = { id: 1 };
  const keyFunc = function() {};
  
  // Object
  obj[keyObj] = 'object value';
  obj[keyFunc] = 'function value';
  console.log('Object 字符串化键:', obj['[object Object]']); // 'object value'
  
  // Map
  map.set(keyObj, 'map object value');
  map.set(keyFunc, 'map function value');
  console.log('Map 对象键:', map.get(keyObj)); // 'map object value'
  
  // WeakMap
  weakMap.set(keyObj, 'weakmap value');
  console.log('WeakMap 对象键:', weakMap.get(keyObj)); // 'weakmap value'
  
  console.groupEnd();
  
  // 2. 顺序保证
  console.group('顺序保证');
  
  const orderObj = {};
  orderObj.b = 2;
  orderObj.a = 1;
  orderObj.c = 3;
  console.log('Object 键顺序:', Object.keys(orderObj)); // ['b', 'a', 'c'] 或类似
  
  const orderMap = new Map();
  orderMap.set('b', 2);
  orderMap.set('a', 1);
  orderMap.set('c', 3);
  console.log('Map 键顺序:', Array.from(orderMap.keys())); // ['b', 'a', 'c']
  
  console.groupEnd();
  
  // 3. 大小获取
  console.group('大小获取');
  
  const sizeObj = { a: 1, b: 2, c: 3 };
  console.log('Object 大小:', Object.keys(sizeObj).length); // 3
  
  const sizeMap = new Map([['a',1], ['b',2], ['c',3]]);
  console.log('Map 大小:', sizeMap.size); // 3
  
  console.groupEnd();
  
  // 4. 迭代方式
  console.group('迭代方式');
  
  const iterObj = { name: '张三', age: 25 };
  console.log('Object 迭代:');
  for (const [key, value] of Object.entries(iterObj)) {
    console.log(key, value);
  }
  
  const iterMap = new Map([['name', '张三'], ['age', 25]]);
  console.log('Map 迭代:');
  for (const [key, value] of iterMap) {
    console.log(key, value);
  }
  
  console.groupEnd();
}

// 5. 内存泄漏演示
function memoryLeakDemo() {
  console.group('内存管理对比');
  
  let mapLeak = new Map();
  let weakMapNoLeak = new WeakMap();
  
  (function() {
    const obj1 = { name: '会泄漏的对象' };
    const obj2 = { name: '不会泄漏的对象' };
    
    mapLeak.set(obj1, '数据');
    weakMapNoLeak.set(obj2, '数据');
  })();
  
  // 此时 obj1 仍被 mapLeak 引用,不会被回收
  // obj2 只被 weakMapNoLeak 弱引用,可以被回收
  
  console.log('Map 仍有引用,可能造成内存泄漏');
  console.log('WeakMap 不会阻止垃圾回收');
  
  console.groupEnd();
}

// 运行对比
compareStructures();
memoryLeakDemo();

五、总结

5.1 选择建议

使用 Object :

  • 需要存储简单的键值对,键是字符串

  • 需要创建对象实例(类实例)

  • 需要 JSON 序列化

  • 需要原型继承

  • 结构化数据(如配置、DTO)

使用 Map :

  • 键不是字符串(对象、函数等)

  • 需要频繁增删键值对

  • 需要保持插入顺序

  • 需要便捷的大小属性(size)

  • 需要频繁迭代

使用 WeakMap :

  • 键必须是对象

  • 需要自动内存管理,防止内存泄漏

  • 存储 DOM 节点的元数据

  • 实现私有属性

  • 不需要迭代操作

5.2 核心区别对比


相关推荐
fareast_mzh1 小时前
Mistral AI本地部署 C++无需Nvidiad独立显卡也能运行(CPU推理)
开发语言·c++·人工智能
Jackey_Song_Odd1 小时前
Part 1:Python语言核心 - Control Flow 控制流
开发语言·windows·python
m0_716667071 小时前
C++中的访问者模式高级应用
开发语言·c++·算法
大鹏说大话1 小时前
构建高并发缓存系统:架构设计、Redis策略与灾难防御
开发语言
Oueii1 小时前
C++中的访问者模式变体
开发语言·c++·算法
2401_838683372 小时前
单元测试在C++项目中的实践
开发语言·c++·算法
guygg882 小时前
基于Kaimal谱的风速时间序列生成MATLAB程序
开发语言·matlab
daols882 小时前
vue表格vxe-table实现表头合并,分组表头自定义合并
前端·vue.js·vxe-table
执行部之龙2 小时前
js手写——防抖
开发语言·前端·javascript