三者的基本概念
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各有其适用场景,理解它们的特点和差异,能帮助我们在不同情况下选择最合适的数据结构,写出更高效、更清晰的代码。