【JavaScript】Map 与对象的区别及迭代方式解析

JavaScript 中的 Map 和对象(Object)都是用于存储键值对的数据结构,但它们在底层实现、性能、适用场景等方面存在显著区别。本文将深入探讨它们的异同,并分析 for (let [key, value] of map)for (let [key, value] of map.entries()) 语法是否等效。

一、Map 与对象的核心区别

1. 语法与声明方式

在 JavaScript 中,声明一个对象通常使用大括号 {},而 Map 需要通过 new Map() 来创建:

// 创建对象
const obj = {
    
     key: 'value' };

// 创建 Map
const map = new Map();
map.set('key', 'value');

对象的键通常是字符串(也可以是 Symbol),而 Map 允许任何数据类型作为键,包括对象、函数等:

const objKey = {
    
    };
const mapExample = new Map();
mapExample.set(objKey, 'object as key');

console.log(mapExample.get(objKey)); // "object as key"

对象的键会被强制转换为字符串,而 Map 则保持键的类型,这使得 Map 在存储复杂数据时更具优势。

2. 键的存储顺序

对象的键是无序的,而 Map 的键是有序的,意味着它会按照插入顺序迭代键值对:

const objExample = {
    
     b: 1, a: 2 };
console.log(Object.keys(objExample)); // 可能输出 ["b", "a"] 或 ["a", "b"]

const mapExample2 = new Map();
mapExample2.set('b', 1);
mapExample2.set('a', 2);
console.log([...mapExample2.keys()]); // 输出 ["b", "a"]

在某些情况下,对象的键顺序可能与插入顺序不同,而 Map 总是按照插入顺序维护键值对,这在需要顺序遍历时非常重要。

3. 迭代方式的差异

对象的键可以使用 Object.keys()Object.values()Object.entries() 获取,然后结合 forEachfor...of 进行迭代:

const objSample = {
    
     a: 1, b: 2 };
Object.entries(objSample).forEach(([key, value]) => {
    
    
  console.log(key, value);
});

Map 提供了更直接的迭代方式,如 map.keys()map.values()map.entries(),并且可以直接用 for...of 遍历:

const mapSample = new Map([
  ['a', 1],
  ['b', 2]
]);

for (let [key, value] of mapSample) {
    
    
  console.log(key, value);
}

Map 直接支持迭代,而对象需要借助 Object.entries() 才能进行类似的迭代。

二、Map 和对象的适用场景

1. 何时使用对象(Object)

  • 需要简单的键值对存储,且键始终是字符串或 Symbol 时
  • 作为 JSON 数据的一部分进行序列化和传输
  • 需要继承 Object.prototype 的方法,例如 hasOwnProperty()toString()

2. 何时使用 Map

  • 需要高效的键值对查找和删除操作
  • 需要以任意类型(对象、函数等)作为键
  • 需要保证键值对的插入顺序
  • 需要更直观的迭代方式

三、Map 的 for (let [key, value] of map)for (let [key, value] of map.entries()) 是否相同

在 JavaScript 的 Map 中,for (let [key, value] of map)for (let [key, value] of map.entries()) 是等价的。

1. for...of 遍历 Map

当直接对 map 进行 for...of 迭代时,默认行为是遍历 map.entries(),即键值对:

const mapExample = new Map([
  ['a', 1],
  ['b', 2],
]);

for (let [key, value] of mapExample) {
    
    
  console.log(key, value);
}

// 输出
// a 1
// b 2

实际上,Map 的默认迭代器就是 map.entries(),因此 for (let [key, value] of map) 本质上等同于 for (let [key, value] of map.entries())

2. 显式调用 map.entries()

for (let [key, value] of mapExample.entries()) {
    
    
  console.log(key, value);
}

// 输出
// a 1
// b 2

3. 验证 map[Symbol.iterator] 默认等于 map.entries()

我们可以通过 Symbol.iterator 验证 Map 的默认迭代行为:

console.log(mapExample[Symbol.iterator] === mapExample.entries); // true

由于 MapSymbol.iterator 方法默认返回 entries(),因此 for (let [key, value] of map)for (let [key, value] of map.entries()) 具有完全相同的效果。

四、注意事项

1. Map 的性能优势

在大数据量的情况下,Map 通常比对象性能更优,因为 Map 使用哈希表实现,查找和删除操作的时间复杂度通常为 O(1),而对象的查找可能涉及哈希冲突或原型链查找,可能更慢。

2. Map 不能使用 JSON.stringify()

如果你尝试对 Map 使用 JSON.stringify(),会得到 undefined,因为 Map 不是原生的 JSON 格式:

const mapData = new Map([
  ['name', 'Alice'],
  ['age', 25]
]);

console.log(JSON.stringify(mapData)); // "{}"

若要序列化 Map,可以先转换为数组:

console.log(JSON.stringify([...mapData])); // '[["name","Alice"],["age",25]]'

3. 对象的 hasOwnProperty 适用于检测属性

如果你需要检查某个键是否存在于对象中,hasOwnProperty() 是一个常用方法:

const objSample = {
    
     a: 1 };
console.log(objSample.hasOwnProperty('a')); // true
console.log(objSample.hasOwnProperty('b')); // false

但在 Map 中,我们应使用 has() 方法:

const mapSample = new Map([['a', 1]]);
console.log(mapSample.has('a')); // true
console.log(mapSample.has('b')); // false

推荐:


在这里插入图片描述