Get to know MDN better
此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。
Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值)都可以作为键或值。
Map 对象是键值对的集合。Map 中的一个键只能出现一次;它在 Map 的集合中是独一无二的。Map 对象按键值对迭代——一个 for...of 循环在每次迭代后会返回一个形式为 [key, value] 的数组。迭代按插入顺序进行,即键值对按 set() 方法首次插入到集合中的顺序(也就是说,当调用 set() 时,map 中没有具有相同值的键)进行迭代。
规范要求 map 实现“平均访问时间与集合中的元素数量呈次线性关系”。因此,它可以在内部表示为散列表(查找复杂度为 O(1))、搜索树(查找复杂度为 O(log(N)))或任何其他数据结构,只要复杂度小于 O(N)。
键的比较基于零值相等算法。(它曾经使用同值相等,将 0 和 -0 视为不同。参见浏览器兼容性。)这意味着 NaN 是与 NaN 相等的(虽然 NaN !== NaN),剩下所有其他的值是根据 === 运算符的结果判断是否相等。此外,对于对象键,相等性基于对象标识。它们是通过引用进行比较,而非通过值。参见使用 Map 对象示例。
Object 和 Map 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象(Object)当成 Map 使用。
不过,它们之间有一些重要的区别,是的在一些情况下使用 Map 会是更好的选择:
| Map 默认不包含任何键。它只包含显式存入的键值对。 |
Object 有原型,因此它包含默认的键,如果不小心的话,它们可能会与你自己的键相冲突。 备注:这可以通过使用 Object.create(null) 来绕过,但很少这样做。 |
| Map 可以安全地与用户提供的键值一起使用。 |
在 Object 上设置用户提供的键值对可能会允许攻击者覆盖对象的原型,这可能会导致对象注入攻击或原型污染攻击。就像意外的键问题一样,这也可以通过使用 null 原型对象来缓解。 |
| Map 的键可以为任何值(包括函数、对象或任何原始值)。 | Object 的键必须为 String 或 Symbol。 |
|
Map 中的键以简单、直接的方式排序:Map 对象按照插入的顺序迭代条目、键和值。 |
尽管现在普通的 Object 的键是有序的,但情况并非总是如此,并且其排序比较复杂的。因此,最好不要依赖属性的顺序。 该顺序最初仅在 ECMAScript 2015 中为自有属性定义;ECMAScript 2020 还定义了继承属性的顺序。但请注意,没有单一机制可以迭代对象的所有属性;各种机制各自包含不同的属性子集。(for-in 仅包含可枚举的字符串键属性;Object.keys 仅包含可枚举的自有字符串键属性;Object.getOwnPropertyNames 包括自有的字符串键属性,即使是不可枚举的;Object.getOwnPropertySymbols 仅对 Symbol 键属性执行相同的操作,等等。) |
| Map 中的项目数量很容易从其 size 属性中获得。 | 确定 Object 中的项目数量通常更麻烦,效率也较低。一种常见的方法是通过获取 Object.keys() 返回的数组的长度。 |
| Map 是可迭代对象,所以它可以直接迭代。 |
Object 没有实现迭代协议,因此对象默认情况下不能直接通过 JavaScript 的 for...of 语句进行迭代。 备注:
|
|
在涉及频繁添加和删除键值对的场景中表现更好。 |
未针对频繁添加和删除键值对进行优化。 |
|
没有对序列化或解析的原生支持。 (但你可以通过使用 JSON.stringify() 及其 replacer 参数和 JSON.parse() 及其 reviver 参数来为 Map 构建自己的序列化和解析支持。参见 Stack Overflow 问题 How do you JSON.stringify an ES6 Map?)。 |
原生支持使用 JSON.stringify() 序列化 Object 到 JSON。 原生支持使用 JSON.parse() 解析 JSON 为 Object。 |
设置对象属性同样适用于 Map 对象,但可能会造成相当大的困扰。
因此,这种方式看起来能工作:
但这种设置属性的方式不会改变 Map 的数据结构。它使用的是通用对象的特性。'bla' 的值未被存储在 Map 中,无法被查询到。其他的对这一数据的操作也会失败:
正确的存储数据到 Map 中的方式是使用 set(key, value) 方法。
浏览器类 Map 对象(或称为“maplike 对象”)是其行为在很多方面都类似于 Map 的 Web API 接口。
就像 Map 一样,对象中的条目可以以添加的顺序迭代。类似 Map 的对象和 Map 具有相同的属性和方法。但是,与 Map 不同的是,它们仅允许每个条目中的键和值具有特定预定义的类型。
允许的类型规范的 IDL 定义给出。例如,RTCStatsReport 是一个类似 Map 的对象,必须使用字符串作为键,对象作为值。这是在规范 IDL 中定义的:
类 Map 对象可以是只读的,也可以是可写的(参见上面 IDL 中的 readonly 关键字)。
除了对键和值类型的限制外,其方法和属性的行为与 Map 中的对应实体相同。
以下是浏览器中只读的类 Map 对象的示例:
创建 Map 对象。
用于创建派生对象的构造函数。
根据提供的回调函数返回的值将给定的可迭代对象分组。最终返回的 Map 对象使用测试函数返回的唯一值作为键,可用于获取每个组的元素数组。
这些属性在 Map.prototype 上定义,并由所有 Map 实例共享。
Map.prototype.constructor创建实例对象的构造函数。对于 Map 实例,初始值为 Map 构造函数。
Map.prototype.size返回 Map 对象中的键值对数量。
Map.prototype[Symbol.toStringTag][Symbol.toStringTag] 属性的初始值是字符串 "Map"。该属性在 Object.prototype.toString() 中使用。
移除 Map 对象中所有的键值对。
Map.prototype.delete()移除 Map 对象中由指定的键标识的条目。
Map.prototype.entries()返回一个新的迭代器对象,其包含 Map 对象中所有键值对 [key, value] 二元数组,以插入顺序排列。
Map.prototype.forEach()以插入顺序为 Map 对象中的每个键值对调用一次 callbackFn。如果为 forEach 提供了 thisArg 参数,则它将作为每一次 callback 的 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] 二元数组,以插入顺序排列。
NaN 也可以作为键。虽然 NaN 与任何值甚至与自己都不相等(NaN !== NaN 返回 true),但是因为所有的 NaN 的值都是无法区分的,所以下面的例子成立:
Map 可以使用 for...of 循环来实现迭代:
Map 也可以通过 forEach() 方法迭代:
Map 能像数组一样被复制:
备注:请记住,数据本身未被克隆。换句话说,这只是对 Map 的浅拷贝。
Map 对象间可以进行合并,但是会保持键的唯一性。
Map 对象也能与数组合并:
| ECMAScript® 2027 Language Specification # sec-map-objects |
启用 JavaScript 以查看此浏览器兼容性表。