Map

Baseline 广泛可用 *

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

* 此特性的某些部分可能存在不同级别的支持。

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(无论是对象还是原始值)都可以用作键或值。

试一试

const map = new Map();

map.set("a", 1);
map.set("b", 2);
map.set("c", 3);

console.log(map.get("a"));
// Expected output: 1

map.set("a", 97);

console.log(map.get("a"));
// Expected output: 97

console.log(map.size);
// Expected output: 3

map.delete("b");

console.log(map.size);
// Expected output: 2

描述

Map 对象是键值对的集合。Map 中的键只能出现一次;它在 Map 集合中是唯一的。Map 对象按键值对进行迭代 — 一个 for...of 循环在每次迭代中返回一个包含 [key, value] 的两元素数组。迭代按插入顺序进行,这对应于每个键值对首次通过 set() 方法插入到 Map 中的顺序(也就是说,当调用 set() 时,Map 中还没有具有相同值的键)。

规范要求 Map 的实现“平均访问时间应低于集合中元素数量的次线性”。因此,它可以在内部表示为哈希表(O(1) 查找)、搜索树(O(log(N)) 查找)或任何其他数据结构,只要其复杂度优于 O(N)。

键的相等性

值的相等性基于 SameValueZero 算法。(它曾经使用 SameValue,该算法将 0-0 视为不同。请查看浏览器兼容性。)这意味着 NaN 被认为是与 NaN 相同的(即使 NaN !== NaN 为 true),所有其他值都根据 === 运算符的语义被视为相等。此外,对于对象键,相等性基于对象标识。它们通过引用而非值进行比较。有关示例,请参阅使用 Map 对象

Object 与 Map

Object 类似于 Map——两者都允许你将键设置为值、检索这些值、删除键以及检测某个键是否存储了内容。因此(并且因为没有内置替代方案),Object 历史上一直被用作 Map

然而,在某些情况下,Map 具有重要的差异,使其更优越:

Map Object
意外的键 默认情况下,Map 不包含任何键。它只包含显式放入其中的内容。

Object 有一个原型,因此它包含默认键,如果你不小心,这些键可能会与你自己的键冲突。

注意:这可以通过使用 Object.create(null) 来绕过,但这很少被使用。

安全 Map 可以安全地与用户提供的键和值一起使用。

Object 上设置用户提供的键值对可能会允许攻击者覆盖对象的原型,这可能导致对象注入攻击原型污染攻击。像意外键问题一样,这也可以通过使用 null-原型对象来缓解。

键类型 Map 的键可以是任何值(包括函数、对象或任何原始值)。 Object 的键必须是 StringSymbol
键的顺序

Map 中的键以直接的方式排序:Map 对象按条目插入的顺序迭代条目、键和值。

尽管普通 Object 的键现在是有序的,但这并非总是如此,并且顺序很复杂。因此,最好不要依赖属性顺序。

顺序最初仅在 ECMAScript 2015 中为自有属性定义;ECMAScript 2020 也为继承属性定义了顺序。但请注意,没有单一机制可以迭代对象的所有属性;各种机制都包含不同的属性子集。(for-in 只包含可枚举的字符串键属性;Object.keys 只包含自有、可枚举的字符串键属性;Object.getOwnPropertyNames 包含自有、字符串键属性,即使是不可枚举的;Object.getOwnPropertySymbols 对仅 Symbol 键属性执行相同的操作,等等。)

大小

Map 中的项目数量可以轻松地通过其 size 属性检索。 确定 Object 中的项目数量则更为间接且效率低下。一种常见的方法是通过 Object.keys() 返回的数组的 length 属性。
迭代 Map 是一个可迭代对象,因此可以直接迭代。

Object 不实现迭代协议,因此对象不能直接使用 JavaScript 的 for...of 语句进行迭代(默认情况下)。

备注

  • 一个对象可以实现迭代协议,或者你可以使用 Object.keysObject.entries 为对象获取一个可迭代对象。
  • for...in 语句允许你迭代对象的可枚举属性。
性能

在涉及频繁添加和删除键值对的场景中表现更好。

未针对频繁添加和删除键值对进行优化。

序列化和解析

没有原生支持序列化或解析。

(但你可以通过使用带有 replacer 参数的 JSON.stringify() 和带有 reviver 参数的 JSON.parse() 来为 Map 构建自己的序列化和解析支持。请参阅 Stack Overflow 问题 How do you JSON.stringify an ES6 Map?)。

使用 JSON.stringify()Object 到 JSON 的原生序列化支持。

使用 JSON.parse() 从 JSON 到 Object 的原生解析支持。

设置对象属性

设置对象属性也适用于 Map 对象,并且可能导致相当大的混淆。

因此,这看起来是可行的:

js
const wrongMap = new Map();
wrongMap["bla"] = "blaa";
wrongMap["bla2"] = "blaaa2";

console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }

但是,这种设置属性的方式不会与 Map 数据结构交互。它使用的是通用对象的特性。’bla’ 的值不会存储在 Map 中进行查询。对数据的其他操作将失败。

js
wrongMap.has("bla"); // false
wrongMap.delete("bla"); // false
console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }

将数据存储在 Map 中的正确用法是通过 set(key, value) 方法。

js
const contacts = new Map();
contacts.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" });
contacts.has("Jessie"); // true
contacts.get("Hilary"); // undefined
contacts.set("Hilary", { phone: "617-555-4321", address: "321 S 2nd St" });
contacts.get("Jessie"); // {phone: "213-555-1234", address: "123 N 1st Ave"}
contacts.delete("Raymond"); // false
contacts.delete("Jessie"); // true
console.log(contacts.size); // 1

类似 Map 的浏览器 API

浏览器中类似 Map 的对象(或“类 Map 对象”)是 Web API 接口,它们在许多方面行为类似于 Map

就像 Map 一样,条目可以按照它们添加到对象的相同顺序进行迭代。类似 Map 的对象和 Map 也具有相同名称和行为的属性和方法。但是,与 Map 不同的是,它们只允许为每个条目的键和值使用特定的预定义类型。

允许的类型在规范 IDL 定义中设置。例如,RTCStatsReport 是一个类似 Map 的对象,它必须使用字符串作为键,对象作为值。这在下面的规范 IDL 中定义:

webidl
interface RTCStatsReport {
  readonly maplike<DOMString, object>;
};

类 Map 对象是只读或读写(参见上面 IDL 中的 readonly 关键字)。

这些方法和属性与 Map 中对应的实体具有相同的行为,除了键和值类型的限制。

以下是只读的类 Map 浏览器对象的示例:

构造函数

Map()

创建一个新的 Map 对象。

静态属性

Map[Symbol.species]

用于创建派生对象的构造函数。

静态方法

Map.groupBy()

使用提供的回调函数返回的值对给定可迭代对象的元素进行分组。最终返回的 Map 使用测试函数中的唯一值作为键,这些键可用于获取每个组中的元素数组。

实例属性

这些属性在 Map.prototype 上定义,并由所有 Map 实例共享。

Map.prototype.constructor

创建实例对象的构造函数。对于 Map 实例,初始值是 Map 构造函数。

Map.prototype.size

返回 Map 对象中的键/值对的数量。

Map.prototype[Symbol.toStringTag]

[Symbol.toStringTag] 属性的初始值是字符串 "Map"。此属性用于 Object.prototype.toString()

实例方法

Map.prototype.clear()

Map 对象中移除所有键值对。

Map.prototype.delete()

从此 Map 中移除由键指定的条目。

Map.prototype.entries()

返回一个新的迭代器对象,其中包含 Map 对象中每个元素的 [key, value] 两元素数组,按插入顺序排列。

Map.prototype.forEach()

Map 对象中存在的每个键值对调用 callbackFn 一次,按插入顺序。如果向 forEach 提供了 thisArg 参数,它将用作每次回调的 this 值。

Map.prototype.get()

返回此 Map 中与键对应的值,如果没有则返回 undefined

Map.prototype.getOrInsert() 实验性

返回此 Map 中与指定键对应的值。如果键不存在,它将插入一个新条目,其中包含键和给定的默认值,并返回插入的值。

Map.prototype.getOrInsertComputed() 实验性

返回此 Map 中与指定键对应的值。如果键不存在,它将插入一个新条目,其中包含键和从给定回调计算的默认值,并返回插入的值。

Map.prototype.has()

返回一个布尔值,指示此 Map 中是否存在具有指定键的条目。

Map.prototype.keys()

返回一个新的迭代器对象,其中包含 Map 对象中每个元素的键,按插入顺序排列。

Map.prototype.set()

向此 Map 添加一个具有指定键和值的新条目,如果键已存在,则更新现有条目。

Map.prototype.values()

返回一个新的迭代器对象,其中包含 Map 对象中每个元素的值,按插入顺序排列。

Map.prototype[Symbol.iterator]()

返回一个新的迭代器对象,其中包含 Map 对象中每个元素的 [key, value] 两元素数组,按插入顺序排列。

示例

使用 Map 对象

js
const myMap = new Map();

const keyString = "a string";
const keyObj = {};
const keyFunc = () => {};

// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");

console.log(myMap.size); // 3

// getting the values
console.log(myMap.get(keyString)); // "value associated with 'a string'"
console.log(myMap.get(keyObj)); // "value associated with keyObj"
console.log(myMap.get(keyFunc)); // "value associated with keyFunc"

console.log(myMap.get("a string")); // "value associated with 'a string'", because keyString === 'a string'
console.log(myMap.get({})); // undefined, because keyObj !== {}
console.log(myMap.get(() => {})); // undefined, because keyFunc !== () => {}

将 NaN 用作 Map 键

NaN 也可以用作键。尽管每个 NaN 都不等于它自己(NaN !== NaN 为 true),但以下示例仍然有效,因为 NaNs 彼此之间无法区分:

js
const myMap = new Map();
myMap.set(NaN, "not a number");

myMap.get(NaN);
// "not a number"

const otherNaN = Number("foo");
myMap.get(otherNaN);
// "not a number"

使用 for...of 迭代 Map

可以使用 for...of 循环迭代 Map:

js
const myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");

for (const [key, value] of myMap) {
  console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one

for (const key of myMap.keys()) {
  console.log(key);
}
// 0
// 1

for (const value of myMap.values()) {
  console.log(value);
}
// zero
// one

for (const [key, value] of myMap.entries()) {
  console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one

使用 forEach() 迭代 Map

可以使用 forEach() 方法迭代 Map:

js
myMap.forEach((value, key) => {
  console.log(`${key} = ${value}`);
});
// 0 = zero
// 1 = one

与 Array 对象的关联

js
const kvArray = [
  ["key1", "value1"],
  ["key2", "value2"],
];

// Use the regular Map constructor to transform a 2D key-value Array into a map
const myMap = new Map(kvArray);

console.log(myMap.get("key1")); // "value1"

// Use Array.from() to transform a map into a 2D key-value Array
console.log(Array.from(myMap)); // Will show you exactly the same Array as kvArray

// A succinct way to do the same, using the spread syntax
console.log([...myMap]);

// Or use the keys() or values() iterators, and convert them to an array
console.log(Array.from(myMap.keys())); // ["key1", "key2"]

克隆和合并 Map

就像 Array 一样,Map 也可以被克隆:

js
const original = new Map([[1, "one"]]);

const clone = new Map(original);

console.log(clone.get(1)); // one
console.log(original === clone); // false (useful for shallow comparison)

注意:请记住,数据本身不会被克隆。换句话说,这只是 Map浅拷贝

Map 可以合并,同时保持键的唯一性:

js
const first = new Map([
  [1, "one"],
  [2, "two"],
  [3, "three"],
]);

const second = new Map([
  [1, "uno"],
  [2, "dos"],
]);

// Merge two maps. The last repeated key wins.
// Spread syntax essentially converts a Map to an Array
const merged = new Map([...first, ...second]);

console.log(merged.get(1)); // uno
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three

Map 也可以与数组合并:

js
const first = new Map([
  [1, "one"],
  [2, "two"],
  [3, "three"],
]);

const second = new Map([
  [1, "uno"],
  [2, "dos"],
]);

// Merge maps with an array. The last repeated key wins.
const merged = new Map([...first, ...second, [1, "un"]]);

console.log(merged.get(1)); // un
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three

规范

规范
ECMAScript® 2026 语言规范
# sec-map-objects

浏览器兼容性

另见