映射

Map 对象保存键值对并记住键的原始插入顺序。任何值(对象和 primitive values)都可以用作键或值。

¥The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.

Try it

描述

¥Description

Map 对象是键值对的集合。Map 中的一个键只能出现一次;它在 Map 系列中是独一无二的。Map 对象通过键值对进行迭代 - for...of 循环为每次迭代返回 [key, value] 的 2 成员数组。迭代按照插入顺序进行,这对应于 set() 方法首先将每个键值对插入到映射中的顺序(即,当调用 set() 时,映射中不存在具有相同值的键) )。

¥Map objects are collections of key-value pairs. A key in the Map may only occur once; it is unique in the Map's collection. A Map object is iterated by key-value pairs — a for...of loop returns a 2-member array of [key, value] for each iteration. Iteration happens in insertion order, which corresponds to the order in which each key-value pair was first inserted into the map by the set() method (that is, there wasn't a key with the same value already in the map when set() was called).

该规范要求映射要实现 "平均而言,提供的访问时间与集合中元素的数量呈次线性关系"。因此,它可以在内部表示为哈希表(具有 O(1) 查找)、搜索树(具有 O(log(N)) 查找)或任何其他数据结构,只要复杂度优于 O (N)。

¥The specification requires maps to be implemented "that, on average, provide access times that are sublinear on the number of elements in the collection". Therefore, it could be represented internally as a hash table (with O(1) lookup), a search tree (with O(log(N)) lookup), or any other data structure, as long as the complexity is better than O(N).

关键平等

¥Key equality

值相等基于 SameValueZero 算法。(以前使用 SameValue,将 0-0 视为不同的。检查 浏览器兼容性。)这意味着 NaN 被认为与 NaN 相同(即使 NaN !== NaN),并且根据 === 运算符的语义,所有其他值都被认为是相等的。

¥Value equality is based on the SameValueZero algorithm. (It used to use SameValue, which treated 0 and -0 as different. Check browser compatibility.) This means NaN is considered the same as NaN (even though NaN !== NaN) and all other values are considered equal according to the semantics of the === operator.

对象与映射

¥Objects vs. Maps

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

¥Object is similar to Map—both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key. For this reason (and because there were no built-in alternatives), Object has been used as Map historically.

然而,在某些情况下,存在一些重要的差异,使得 Map 更可取:

¥However, there are important differences that make Map preferable in some cases:

映射 对象
意外按键 默认情况下, 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
迭代 Mapiterable,因此可以直接迭代。

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

注意:

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

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

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

序列化和解析

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

(但是你可以通过使用 JSON.stringify() 及其 replacer 参数以及使用 JSON.parse() 及其 reviver 参数来构建自己的对 Map 的序列化和解析支持。请参阅堆栈溢出问题 如何对 ES6 映射进行 JSON.stringify?)。

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

原生支持使用 JSON.parse() 从 JSON 解析为 Object

设置对象属性

¥Setting object properties

设置对象属性也适用于 Map 对象,但可能会导致相当大的混乱。

¥Setting Object properties works for Map objects as well, and can cause considerable confusion.

因此,这似乎以某种方式起作用:

¥Therefore, this appears to work in a way:

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

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

但这种设置属性的方式不会与 Map 数据结构交互。它利用了通用对象的特性。'bla' 的值不存储在 Map 中以供查询。对数据的其他操作失败:

¥But that way of setting a property does not interact with the Map data structure. It uses the feature of the generic object. The value of 'bla' is not stored in the Map for queries. Other operations on the data fail:

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

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

¥The correct usage for storing data in the Map is through the set(key, value) method.

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

类似映射的浏览器 API

¥Map-like browser APIs

类似浏览器 Map 的对象(或 "类似地图的对象")是 Web API 接口,其行为在很多方面与 Map 类似。

¥Browser Map-like objects (or "maplike objects") are Web API interfaces that behave in many ways like a Map.

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

¥Just like Map, entries can be iterated in the same order that they were added to the object. Map-like objects and Map also have properties and methods that share the same name and behavior. However unlike Map they only allow specific predefined types for the keys and values of each entry.

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

¥The allowed types are set in the specification IDL definition. For example, RTCStatsReport is a Map-like object that must use strings for keys and objects for values. This is defined in the specification IDL below:

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

类似 Map 的对象要么是只读的,要么是可读写的(请参阅上面 IDL 中的 readonly 关键字)。

¥Map-like objects are either read-only or read-writable (see the readonly keyword in the IDL above).

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

¥The methods and properties have the same behavior as the equivalent entities in Map, except for the restriction on the types of the keys and values.

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

¥The following are examples of read-only Map-like browser objects:

构造函数

¥Constructor

Map()

创建一个新的 Map 对象。

静态属性

¥Static properties

Map[@@species]

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

静态方法

¥Static methods

Map.groupBy()

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

实例属性

¥Instance properties

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

¥These properties are defined on Map.prototype and shared by all Map instances.

Map.prototype.constructor

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

Map.prototype.size

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

Map.prototype[@@toStringTag]

@@toStringTag 属性的初始值为字符串 "Map"。该属性在 Object.prototype.toString() 中使用。

实例方法

¥Instance methods

Map.prototype.clear()

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

Map.prototype.delete()

如果 Map 对象中的元素存在并已被删除,则返回 true;如果该元素不存在,则返回 false。之后 map.has(key) 将返回 false

Map.prototype.entries()

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

Map.prototype.forEach()

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

Map.prototype.get()

返回与传递的键关联的值,如果没有则返回 undefined

Map.prototype.has()

返回一个布尔值,指示某个值是否已与 Map 对象中传递的键关联。

Map.prototype.keys()

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

Map.prototype.set()

设置 Map 对象中传递的键的值。返回 Map 对象。

Map.prototype.values()

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

Map.prototype[@@iterator]()

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

示例

¥Examples

使用映射对象

¥Using the Map object

js
const myMap = new Map();

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

// 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(function () {})); // undefined, because keyFunc !== function () {}

使用 NaN 作为 Map 键

¥Using NaN as Map keys

NaN 也可以用作密钥。即使每个 NaN 都不等于自身(NaN !== NaN 为真),以下示例仍然有效,因为 NaN 彼此无法区分:

¥NaN can also be used as a key. Even though every NaN is not equal to itself (NaN !== NaN is true), the following example works because NaNs are indistinguishable from each other:

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 迭代映射

¥Iterating Map with for...of

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

¥Maps can be iterated using a for...of loop:

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() 迭代映射

¥Iterating Map with forEach()

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

¥Maps can be iterated using the forEach() method:

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

与数组对象的关系

¥Relation with Array objects

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"]

克隆和合并映射

¥Cloning and merging Maps

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

¥Just like Arrays, Maps can be cloned:

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)

注意:请记住,数据本身并未被克隆。

¥Note: Keep in mind that the data itself is not cloned.

可以合并地图,保持键的唯一性:

¥Maps can be merged, maintaining key uniqueness:

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

映射也可以与数组合并:

¥Maps can be merged with Arrays, too:

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, "eins"]]);

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

规范

Specification
ECMAScript Language Specification
# sec-map-objects

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看