地图
**Map
** 对象保存键值对,并记住键的原始插入顺序。任何值(包括对象和原始值)都可作为键或值使用。
尝试一下
描述
Map
对象是键值对的集合。Map
中的键**只能出现一次**;它在 Map
的集合中是唯一的。Map
对象按键值对进行迭代 - for...of
循环在每次迭代中返回包含 [key, value]
的 2 元素数组。迭代按插入顺序进行,该顺序对应于每个键值对第一次通过 set()
方法插入到地图中的顺序(也就是说,在调用 set()
时,地图中不存在具有相同值的键)。
规范要求地图的实现“平均情况下提供访问时间,该访问时间对集合中的元素数量呈亚线性”。因此,它可以在内部表示为哈希表(具有 O(1) 查找)、搜索树(具有 O(log(N)) 查找)或任何其他数据结构,只要复杂度优于 O(N) 即可。
键相等
值相等基于 SameValueZero 算法。(它曾经使用 SameValue,该算法将 0
和 -0
视为不同。请查看 浏览器兼容性。)这意味着 NaN
被视为与 NaN
相同(即使 NaN !== NaN
),并且所有其他值根据 ===
运算符的语义被视为相等。
对象与地图
Object
与 Map
相似 - 两者都允许你将键设置为值,检索这些值,删除键,以及检测键是否存在值。出于这个原因(以及因为没有内置的替代方案),历史上 Object
一直用作 Map
。
但是,存在一些重要的差异,在某些情况下使 Map
更加可取。
地图 | 对象 | |
---|---|---|
意外键 | Map 默认不包含任何键。它只包含显式放入其中的内容。 |
注意:这可以通过使用 |
安全性 | Map 可以安全地与用户提供的键和值一起使用。 |
在 |
键类型 | Map 的键可以是任何值(包括函数、对象或任何原始值)。 |
Object 的键必须是 String 或 Symbol 。 |
键顺序 |
|
虽然普通 顺序最初仅在 ECMAScript 2015 中为自有属性定义;ECMAScript 2020 还为继承的属性定义了顺序。但请注意,没有一种机制可以迭代对象的所有属性;各种机制分别包含属性的不同子集。( |
大小 |
Map 中的项目数量可以从它的 size 属性中轻松检索。 |
确定 Object 中的项目数量更加迂回且效率较低。一种常见的方法是通过从 Object.keys() 返回的数组的 length 来确定。 |
迭代 | Map 是一个 可迭代的,因此它可以直接迭代。 |
注意
|
性能 |
在涉及频繁添加和删除键值对的场景中表现更好。 |
未针对频繁添加和删除键值对进行优化。 |
序列化和解析 |
没有对序列化或解析的原生支持。 (但你可以通过使用 |
原生支持从 原生支持从 JSON 到 |
设置对象属性
设置对象属性也适用于 Map 对象,这可能会造成相当大的混淆。
因此,这似乎以一种方式工作
const wrongMap = new Map();
wrongMap["bla"] = "blaa";
wrongMap["bla2"] = "blaaa2";
console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }
但是这种设置属性的方式不与 Map 数据结构交互。它使用通用对象的特性。'bla' 的值不会存储在 Map 中以供查询。对数据的其他操作也会失败。
wrongMap.has("bla"); // false
wrongMap.delete("bla"); // false
console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }
在 Map 中存储数据的正确方法是使用 set(key, value)
方法。
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 中定义
interface RTCStatsReport {
readonly maplike<DOMString, object>;
};
类似 Map
的对象是只读的或可读写的(参见上面 IDL 中的 readonly
关键字)。
- 只读类似
Map
的对象具有属性size
,以及方法:entries()
,forEach()
,get()
,has()
,keys()
,values()
,以及[Symbol.iterator]()
。 - 可写类似
Map
的对象还具有以下方法:clear()
,delete()
,以及set()
。
这些方法和属性与 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
对象中存在元素并且已删除,则返回true
,否则返回false
。之后,map.has(key)
将返回false
。 Map.prototype.entries()
-
返回一个新的 Iterator 对象,该对象包含
Map
对象中每个元素的[key, value]
的两个元素数组,顺序为插入顺序。 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[Symbol.iterator]()
-
返回一个新的 Iterator 对象,该对象包含
Map
对象中每个元素的[key, value]
的两个元素数组,顺序为插入顺序。
示例
使用 Map 对象
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 键
NaN
也可以用作键。尽管每个 NaN
都不等于自身(NaN !== NaN
为真),但以下示例有效,因为 NaN
彼此不可区分
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
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
myMap.forEach((value, key) => {
console.log(`${key} = ${value}`);
});
// 0 = zero
// 1 = one
与数组对象的关联
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
也可以克隆
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 可以合并,保持键的唯一性
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 也可以与数组合并
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
规范
规范 |
---|
ECMAScript 语言规范 # sec-map-objects |
浏览器兼容性
BCD 表格仅在浏览器中加载