js中 Map、 Set 、Object比对

3 阅读4分钟

三者的基本概念

Object(对象)

  • 最基础的键值对集合
  • 键通常是字符串或Symbol
  • JavaScript中最常用的数据结构

Map(映射)

  • ES6引入的键值对集合
  • 键可以是任意类型
  • 有序且可迭代

Set(集合)

  • ES6引入的值的集合
  • 每个值唯一
  • 有序且可迭代

详细对比分析

1. 键的类型

// Object - 键只能是字符串或Symbol
const obj = {
  'name': '张三',
  123: '数字键会被转为字符串',
  [Symbol('id')]: 'symbol键'
};

// Map - 键可以是任意类型
const map = new Map();
map.set('string', '字符串键');
map.set(123, '数字键');
map.set(true, '布尔键');
map.set({}, '对象键');
map.set(() => {}, '函数键');

// Set - 只存储值,不是键值对
const set = new Set();
set.add('字符串');
set.add(123);
set.add({});

2. 大小获取

// Object
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj).length); // 3

// Map
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
console.log(map.size); // 3

// Set
const set = new Set([1, 2, 3]);
console.log(set.size); // 3

3. 添加和访问元素

// Object
const obj = {};
obj.name = '张三';        // 点号语法
obj['age'] = 25;         // 方括号语法
console.log(obj.name);   // '张三'
console.log(obj['age']); // 25

// Map
const map = new Map();
map.set('name', '张三');
map.set('age', 25);
console.log(map.get('name')); // '张三'
console.log(map.get('age'));  // 25

// Set
const set = new Set();
set.add('张三');
set.add(25);
console.log(set.has('张三')); // true
console.log(set.has(30));    // false

4. 删除元素

// Object
const obj = { name: '张三', age: 25 };
delete obj.age;
console.log(obj); // { name: '张三' }

// Map
const map = new Map([['name', '张三'], ['age', 25]]);
map.delete('age');
console.log(map); // Map { 'name' => '张三' }

// Set
const set = new Set(['张三', 25]);
set.delete(25);
console.log(set); // Set { '张三' }

5. 遍历方式

const obj = { a: 1, b: 2, c: 3 };
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
const set = new Set([1, 2, 3]);

// Object遍历
for (let key in obj) {
  console.log(key, obj[key]);
}

Object.keys(obj).forEach(key => {
  console.log(key, obj[key]);
});

// Map遍历
for (let [key, value] of map) {
  console.log(key, value);
}

map.forEach((value, key) => {
  console.log(key, value);
});

// Set遍历
for (let value of set) {
  console.log(value);
}

set.forEach(value => {
  console.log(value);
});

6. 原型和继承

// Object有默认原型
const obj = {};
console.log(obj.toString); // [Function: toString] - 继承自Object.prototype

// 创建无原型的对象
const pureObj = Object.create(null);
console.log(pureObj.toString); // undefined

// Map没有默认键
const map = new Map();
console.log(map.get('toString')); // undefined

// Set也没有默认值
const set = new Set();
console.log(set.has('toString')); // false

性能对比

1. 频繁添加/删除操作

// 性能测试示例(实际结果可能因环境而异)
function performanceTest() {
  const iterations = 100000;
  
  // Object测试
  console.time('Object');
  const obj = {};
  for (let i = 0; i < iterations; i++) {
    obj[i] = i;
  }
  for (let i = 0; i < iterations; i++) {
    delete obj[i];
  }
  console.timeEnd('Object');
  
  // Map测试
  console.time('Map');
  const map = new Map();
  for (let i = 0; i < iterations; i++) {
    map.set(i, i);
  }
  for (let i = 0; i < iterations; i++) {
    map.delete(i);
  }
  console.timeEnd('Map');
}

performanceTest();

2. 查找操作

// 大量数据查找测试
function searchTest() {
  const data = Array.from({length: 10000}, (_, i) => [i, `value${i}`]);
  
  // Object
  const obj = Object.fromEntries(data);
  
  // Map
  const map = new Map(data);
  
  // Set(只测试值的存在性)
  const set = new Set(data.map(([key]) => key));
  
  console.time('Object search');
  for (let i = 0; i < 1000; i++) {
    obj[Math.floor(Math.random() * 10000)];
  }
  console.timeEnd('Object search');
  
  console.time('Map search');
  for (let i = 0; i < 1000; i++) {
    map.get(Math.floor(Math.random() * 10000));
  }
  console.timeEnd('Map search');
  
  console.time('Set search');
  for (let i = 0; i < 1000; i++) {
    set.has(Math.floor(Math.random() * 10000));
  }
  console.timeEnd('Set search');
}

实际应用场景

1. Object适用场景

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

// 数据模型
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

// JSON数据处理
const jsonData = {
  users: [
    { id: 1, name: '张三' },
    { id: 2, name: '李四' }
  ]
};

2. Map适用场景

// 缓存系统(键可能是复杂类型)
const cache = new Map();
const userQuery = { userId: 123, includeProfile: true };
cache.set(userQuery, { name: '张三', profile: {...} });

// DOM元素关联数据
const elementData = new Map();
const button = document.querySelector('#myButton');
elementData.set(button, { clickCount: 0, lastClicked: Date.now() });

// 函数记忆化
const memoCache = new Map();
function fibonacci(n) {
  if (memoCache.has(n)) {
    return memoCache.get(n);
  }
  
  let result;
  if (n <= 1) {
    result = n;
  } else {
    result = fibonacci(n - 1) + fibonacci(n - 2);
  }
  
  memoCache.set(n, result);
  return result;
}

3. Set适用场景

// 数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];

// 权限管理
class UserPermissions {
  constructor() {
    this.permissions = new Set();
  }
  
  addPermission(permission) {
    this.permissions.add(permission);
  }
  
  hasPermission(permission) {
    return this.permissions.has(permission);
  }
  
  removePermission(permission) {
    this.permissions.delete(permission);
  }
}

// 标签系统
const tags = new Set();
tags.add('JavaScript');
tags.add('Node.js');
tags.add('React');

选择建议

何时使用Object?

  • 需要JSON序列化/反序列化
  • 作为记录/字典使用(键为字符串)
  • 需要原型继承
  • 性能要求极高的小数据集

何时使用Map?

  • 键不是字符串类型
  • 需要频繁添加/删除键值对
  • 需要保持插入顺序
  • 键的数量在运行时变化

何时使用Set?

  • 需要存储唯一值
  • 需要快速查找值是否存在
  • 集合操作(并集、交集等)
  • 去重操作

综合示例

// 复杂应用场景:用户管理系统
class UserManager {
  constructor() {
    // 使用Map存储用户数据(ID可能是数字)
    this.users = new Map();
    
    // 使用Set存储活跃用户ID
    this.activeUsers = new Set();
    
    // 使用Object存储配置
    this.config = {
      maxUsers: 1000,
      sessionTimeout: 30 * 60 * 1000 // 30分钟
    };
  }
  
  addUser(id, userData) {
    this.users.set(id, {
      ...userData,
      createdAt: Date.now()
    });
  }
  
  getUser(id) {
    return this.users.get(id);
  }
  
  setUserActive(id) {
    if (this.users.has(id)) {
      this.activeUsers.add(id);
    }
  }
  
  setUserInactive(id) {
    this.activeUsers.delete(id);
  }
  
  isUserActive(id) {
    return this.activeUsers.has(id);
  }
  
  getActiveUserCount() {
    return this.activeUsers.size;
  }
  
  getAllUsers() {
    return Array.from(this.users.entries());
  }
}

// 使用示例
const userManager = new UserManager();
userManager.addUser(1, { name: '张三', email: 'zhang@example.com' });
userManager.addUser('user_2', { name: '李四', email: 'li@example.com' });
userManager.setUserActive(1);

console.log(userManager.isUserActive(1)); // true
console.log(userManager.getActiveUserCount()); // 1

总的来说,Object、Map和Set各有其适用场景,理解它们的特点和差异,能帮助我们在不同情况下选择最合适的数据结构,写出更高效、更清晰的代码。