diff --git a/files/zh-cn/web/javascript/reference/global_objects/map/index.md b/files/zh-cn/web/javascript/reference/global_objects/map/index.md index 97b0956ed213b0..dacdb17bffec29 100644 --- a/files/zh-cn/web/javascript/reference/global_objects/map/index.md +++ b/files/zh-cn/web/javascript/reference/global_objects/map/index.md @@ -5,13 +5,13 @@ slug: Web/JavaScript/Reference/Global_Objects/Map {{JSRef}} -**`Map`** 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者{{Glossary("Primitive", "基本类型")}})都可以作为一个键或一个值。 +**`Map`** 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者{{Glossary("Primitive", "原始值")}})都可以作为键或值。 {{EmbedInteractiveExample("pages/js/map.html", "taller")}} ## 描述 -`Map` 对象是键值对的集合。`Map` 中的一个键**只能出现一次**;它在 `Map` 的集合中是独一无二的。`Map` 对象按键值对迭代——一个 {{jsxref("Statements/for...of", "for...of")}} 循环在每次迭代后会返回一个形式为 `[key,value]` 的数组。迭代按*插入顺序*进行,即键值对按 [`set()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map/set) 方法首次插入到集合中的顺序(也就是说,当调用 `set()` 时,map 中没有具有相同值的键)进行迭代。 +`Map` 对象是键值对的集合。`Map` 中的一个键**只能出现一次**;它在 `Map` 的集合中是独一无二的。`Map` 对象按键值对迭代——一个 {{jsxref("Statements/for...of", "for...of")}} 循环在每次迭代后会返回一个形式为 `[key, value]` 的数组。迭代按*插入顺序*进行,即键值对按 [`set()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map/set) 方法首次插入到集合中的顺序(也就是说,当调用 `set()` 时,map 中没有具有相同值的键)进行迭代。 规范要求 map 实现“平均访问时间与集合中的元素数量呈次线性关系”。因此,它可以在内部表示为哈希表(使用 O(1) 查找)、搜索树(使用 O(log(N)) 查找)或任何其他数据结构,只要复杂度小于 O(N)。 @@ -36,95 +36,107 @@ slug: Web/JavaScript/Reference/Global_Objects/Map 意外的键 - Map 默认情况不包含任何键。只包含显式插入的键。 -

一个 Object 有一个原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

+ Map 默认不包含任何键。它只包含显式存入的键值对。 + + +

+ Object 有原型,因此它包含默认的键,如果不小心的话,它们可能会与你自己的键相冲突。 +

-

备注:虽然可以用 {{jsxref("Object.create", "Object.create(null)")}} 来创建一个没有原型的对象,但是这种用法不太常见。

+

+ 备注:这可以通过使用 {{jsxref("Object.create", "Object.create(null)")}} 来绕过,但很少这样做。 +

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

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

+ + 键的类型 - 一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。 - 一个 Object 的键必须是一个 {{jsxref("String")}} 或是 {{jsxref("Symbol")}}。 + + Map 的键可以为任何值(包括函数、对象或任何原始值)。 + + + Object 的键必须为 {{jsxref("String")}} 或 {{jsxref("Symbol")}}。 + 键的顺序 -

Map 中的键是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。

+

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

-

虽然 Object 的键目前是有序的,但并不总是这样,而且这个顺序是复杂的。因此,最好不要依赖属性的顺序。

-

自 ECMAScript 2015 规范以来,对象的属性被定义为是有序的;ECMAScript 2020 则额外定义了继承属性的顺序。参见 OrdinaryOwnPropertyKeys - 和 - EnumerateObjectProperties 抽象规范说明。但是,请注意没有可以迭代对象所有属性的机制,每一种机制只包含了属性的不同子集。({{jsxref("Statements/for...in","for-in")}} - 仅包含了以字符串为键的属性;{{jsxref("Object.keys")}} - 仅包含了对象自身的、可枚举的、以字符串为键的属性;{{jsxref("Object.getOwnPropertyNames")}} - 包含了所有以字符串为键的属性,即使是不可枚举的;{{jsxref("Object.getOwnPropertySymbols")}} - 与前者类似,但其包含的是以 Symbol 为键的属性,等等。) +

+ 尽管现在普通的 Object 的键是有序的,但情况并非总是如此,并且其排序比较复杂的。因此,最好不要依赖属性的顺序。 +

+

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

- Size - Map 的键值对个数可以轻易地通过 {{jsxref("Map.prototype.size", "size")}} 属性获取。 - Object 的键值对个数只能手动计算。 +

大小

+ + Map 中的项目数量很容易从其 {{jsxref("Map.prototype.size", "size")}} 属性中获得。 + + + 确定 Object 中的项目数量通常更麻烦,效率也较低。一种常见的方法是通过获取 {{jsxref("Object.keys()")}} 返回的数组的{{jsxref("Array/length", "长度", "", 1)}}。 + - 迭代 - Map可迭代的 的,所以可以直接被迭代。 - -

Object 没有实现 迭代协议,所以使用 JavaSctipt 的 for...of 表达式并不能直接迭代对象。

-
-

备注:

- -
- + 迭代 + + Map可迭代对象,所以它可以直接迭代。 + + +

+ Object 没有实现迭代协议,因此对象默认情况下不能直接通过 JavaScript 的 for...of 语句进行迭代。 +

+
+

备注:

+ +
+ 性能 -

在频繁增删键值对的场景下表现更好。

+

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

-

在频繁添加和删除键值对的场景下未作出优化。

+

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

序列化和解析 -

没有元素的序列化和解析的支持。

-

(但是你可以使用携带 replacer 参数的 {{jsxref("JSON.stringify()")}} 创建一个自己的对 Map 的序列化和解析支持。参见 Stack Overflow 上的提问:How do you JSON.stringify an ES6 Map?

+

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

+

+ (但你可以通过使用 {{jsxref("JSON.stringify()")}} 及其 replacer 参数和 {{jsxref("JSON.parse()")}} 及其 reviver 参数来为 Map 构建自己的序列化和解析支持。参见 Stack Overflow 问题 How do you JSON.stringify an ES6 Map?)。 +

-

原生的由 {{jsxref("Object")}} 到 JSON 的序列化支持,使用 {{jsxref("JSON.stringify()")}}。

-

原生的由 JSON 到 {{jsxref("Object")}} 的解析支持,使用 {{jsxref("JSON.parse()")}}。

+

原生支持使用 {{jsxref("JSON.stringify()")}} 序列化 {{jsxref("Object")}} 到 JSON。

+

原生支持使用 {{jsxref("JSON.parse()")}} 解析 JSON 为 {{jsxref("Object")}}。

@@ -166,6 +178,36 @@ contacts.delete("Jessie"); // true console.log(contacts.size); // 1 ``` +### 类 Map 浏览器 API + +**浏览器类 Map 对象**(或称为“maplike 对象”)是其行为在很多方面都类似于 `Map` 的 [Web API](/zh-CN/docs/Web/API) 接口。 + +就像 `Map` 一样,对象中的条目可以以添加的顺序迭代。类似 `Map` 的对象和 `Map` 具有相同的属性和方法。但是,与 `Map` 不同的是,它们仅允许每个条目中的键和值具有特定预定义的类型。 + +允许的类型规范的 IDL 定义给出。例如,{{domxref("RTCStatsReport")}} 是一个类似 `Map` 的对象,必须使用字符串作为键,对象作为值。这是在规范 IDL 中定义的: + +```webidl +interface RTCStatsReport { + readonly maplike; +}; +``` + +类 `Map` 对象可以是只读的,也可以是可写的(参见上面 IDL 中的 `readonly` 关键字)。 + +- 只读的类 `Map` 对象具有 [`size`](#map.prototype.size) 属性,以及这些方法:[`entries()`](#map.prototype.entries)、[`forEach()`](#map.prototype.foreach)、[`keys()`](#map.prototype.keys)、[`values()`](#map.prototype.values) 和 [`@@iterator`](#map.prototypeiterator) 。 +- 可写的类 `Map` 对象还额外具有这些方法:[`clear()`](#map.prototype.clear)、[`delete()`](#map.prototype.delete) 和 [`set()`](#map.prototype.set)。 + +除了对键和值类型的限制外,其方法和属性的行为与 `Map` 中的对应实体相同。 + +以下是浏览器中只读的类 `Map` 对象的示例: + +- {{domxref("AudioParamMap")}} +- {{domxref("RTCStatsReport")}} +- {{domxref("EventCounts")}} +- {{domxref("KeyboardLayoutMap")}} +- {{domxref("MIDIInputMap")}} +- {{domxref("MIDIOutputMap")}} + ## 构造函数 - {{jsxref("Map/Map", "Map()")}} @@ -173,13 +215,24 @@ console.log(contacts.size); // 1 ## 静态属性 -- {{jsxref("Map.@@species", "get Map[@@species]")}} +- {{jsxref("Map/@@species", "Map[@@species]")}} - : 用于创建派生对象的构造函数。 +## 静态方法 + +- {{jsxref("Map.groupBy()")}} + - : 根据提供的回调函数返回的值将给定的可迭代对象分组。最终返回的 `Map` 对象使用测试函数返回的唯一值作为键,可用于获取每个组的元素数组。 + ## 实例属性 +这些属性在 `Map.prototype` 上定义,并由所有 `Map` 实例共享。 + +- {{jsxref("Object/constructor", "Map.prototype.constructor")}} + - : 创建实例对象的构造函数。对于 `Map` 实例,初始值为 {{jsxref("Map/Map", "Map")}} 构造函数。 - {{jsxref("Map.prototype.size")}} - : 返回 `Map` 对象中的键值对数量。 +- `Map.prototype[@@toStringTag]` + - : [`@@toStringTag`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag) 属性的初始值是字符串 `"Map"`。该属性在 {{jsxref("Object.prototype.toString()")}} 中使用。 ## 实例方法 @@ -187,24 +240,22 @@ console.log(contacts.size); // 1 - : 移除 `Map` 对象中所有的键值对。 - {{jsxref("Map.prototype.delete()")}} - : 移除 `Map` 对象中指定的键值对,如果键值对存在并成功被移除,返回 `true`,否则返回 `false`。调用 `delete` 后再调用 `map.has(key)` 将返回 `false`。 +- {{jsxref("Map.prototype.entries()")}} + - : 返回一个新的迭代器对象,其包含 `Map` 对象中所有键值对 `[key, value]` 二元数组,以插入顺序排列。 +- {{jsxref("Map.prototype.forEach()")}} + - : 以插入顺序为 `Map` 对象中的每个键值对调用一次 `callbackFn`。如果为 `forEach` 提供了 `thisArg` 参数,则它将作为每一次 callback 的 `this` 值。 - {{jsxref("Map.prototype.get()")}} - : 返回与指定的键 `key` 关联的值,若不存在关联的值,则返回 `undefined`。 - {{jsxref("Map.prototype.has()")}} - : 返回一个布尔值,用来表明 `Map` 对象中是否存在与指定的键 `key` 关联的值。 +- {{jsxref("Map.prototype.keys()")}} + - : 返回一个新的迭代器对象,其包含 `Map` 对象中所有元素的键,以插入顺序排列。 - {{jsxref("Map.prototype.set()")}} - : 在 `Map` 对象中设置与指定的键 `key` 关联的值,并返回 `Map` 对象。 -- {{jsxref("Map.@@iterator", "Map.prototype[@@iterator]()")}} - - - : 返回一个新的迭代对象,其为一个包含 `Map` 对象中所有键值对的 `[key, value]` 数组,并以插入 `Map` 对象的顺序排列。 - -- {{jsxref("Map.prototype.keys()")}} - - : 返回一个新的迭代对象,其中包含 `Map` 对象中所有的键,并以插入 `Map` 对象的顺序排列。 - {{jsxref("Map.prototype.values()")}} - : 返回一个新的迭代对象,其中包含 `Map` 对象中所有的值,并以插入 `Map` 对象的顺序排列。 -- {{jsxref("Map.prototype.entries()")}} - - : 返回一个新的迭代对象,其为一个包含 `Map` 对象中所有键值对的 `[key, value]` 数组,并以插入 `Map` 对象的顺序排列。 -- {{jsxref("Map.prototype.forEach()")}} - - : 以插入的顺序对 `Map` 对象中存在的键值对分别调用一次 `callbackFn`。如果给定了 `thisArg` 参数,这个参数将会是回调函数中 `this` 的值。 +- [`Map.prototype[@@iterator]()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map/@@iterator) + - : 返回一个新的迭代器对象,其包含 `Map` 对象中所有元素 `[key, value]` 二元数组,以插入顺序排列。 ## 示例 @@ -236,7 +287,7 @@ console.log(myMap.get(function () {})); // undefined,因为 keyFunc !== functi ### 将 NaN 作为 Map 的键 -`NaN` 也可以作为 `Map` 对象的键。虽然 `NaN` 与任何值甚至与自己都不相等(`NaN !== NaN` 返回 true),但是因为所有的 `NaN` 的值都是无法区分的,所以下面的例子成立: +{{jsxref("NaN")}} 也可以作为键。虽然 `NaN` 与任何值甚至与自己都不相等(`NaN !== NaN` 返回 true),但是因为所有的 `NaN` 的值都是无法区分的,所以下面的例子成立: ```js const myMap = new Map(); @@ -250,7 +301,7 @@ myMap.get(otherNaN); // "not a number" ``` -### 使用 for...of 方法迭代 Map +### 使用 for...of 迭代 Map `Map` 可以使用 `for...of` 循环来实现迭代: @@ -284,7 +335,7 @@ for (const [key, value] of myMap.entries()) { // 1 = one ``` -### 使用 forEach() 方法迭代 Map +### 使用 forEach() 迭代 Map `Map` 也可以通过 {{jsxref("Map.prototype.forEach", "forEach()")}} 方法迭代: @@ -296,7 +347,7 @@ myMap.forEach((value, key) => { // 1 = one ``` -### Map 与数组的关系 +### Map 与数组对象的关系 ```js const kvArray = [ @@ -304,12 +355,12 @@ const kvArray = [ ["key2", "value2"], ]; -// 使用常规的 Map 构造函数可以将一个二维键值对数组转换成一个 Map 对象 +// 使用常规的 Map 构造函数可以将一个二维的键值对数组转换成一个 Map 对象 const myMap = new Map(kvArray); console.log(myMap.get("key1")); // "value1" -// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组 +// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维的键值对数组 console.log(Array.from(myMap)); // 输出和 kvArray 相同的数组 // 更简洁的方法来做如上同样的事情,使用展开运算符 @@ -389,8 +440,7 @@ console.log(merged.get(3)); // three ## 参见 -- A polyfill of `Map` is available in - [`core-js`](https://github.com/zloirock/core-js#map) +- [`core-js` 中 `Map` 的 polyfill](https://github.com/zloirock/core-js#map) - {{jsxref("Set")}} - {{jsxref("WeakMap")}} - {{jsxref("WeakSet")}}