Movatterモバイル変換


[0]ホーム

URL:


  1. 面向开发者的 Web 技术
  2. JavaScript
  3. JavaScript 参考
  4. JavaScript 标准内置对象
  5. Map

此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。

View in EnglishAlways switch to English

Map

Baseline Widely available *

This feature is well established and works across many devices and browser versions. It’s been available across browsers since ⁨2015年7月⁩.

* Some parts of this feature may have varying levels of support.

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

尝试一下

const map1 = new Map();map1.set("a", 1);map1.set("b", 2);map1.set("c", 3);console.log(map1.get("a"));// Expected output: 1map1.set("a", 97);console.log(map1.get("a"));// Expected output: 97console.log(map1.size);// Expected output: 3map1.delete("b");console.log(map1.size);// Expected output: 2

描述

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),剩下所有其他的值是根据=== 运算符的结果判断是否相等。

ObjectMap 的比较

ObjectMap 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象当成Map 使用。

不过MapObject 有一些重要的区别,在下列情况中使用Map 会是更好的选择:

MapObject
意外的键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() 返回的数组的长度
迭代Map可迭代对象,所以它可以直接迭代。

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

备注:

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

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

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

序列化和解析

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

(但你可以通过使用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 对象,但容易造成困扰。

即,以下的代码能够正常运行(但不推荐):

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"); // falsewrongMap.delete("bla"); // falseconsole.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"); // truecontacts.get("Hilary"); // undefinedcontacts.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"); // falsecontacts.delete("Jessie"); // trueconsole.log(contacts.size); // 1

类 Map 浏览器 API

浏览器类 Map 对象(或称为“maplike 对象”)是其行为在很多方面都类似于MapWeb API 接口。

就像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 对象中指定的键值对,如果键值对存在并成功被移除,返回true,否则返回false。调用delete 后再调用map.has(key) 将返回false

Map.prototype.entries()

返回一个新的迭代器对象,其包含Map 对象中所有键值对[key, value] 二元数组,以插入顺序排列。

Map.prototype.forEach()

以插入顺序为Map 对象中的每个键值对调用一次callbackFn。如果为forEach 提供了thisArg 参数,则它将作为每一次 callback 的this 值。

Map.prototype.get()

返回与指定的键key 关联的值,若不存在关联的值,则返回undefined

Map.prototype.has()

返回一个布尔值,用来表明Map 对象中是否存在与指定的键key 关联的值。

Map.prototype.keys()

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

Map.prototype.set()

Map 对象中设置与指定的键key 关联的值,并返回Map 对象。

Map.prototype.values()

返回一个新的迭代对象,其中包含Map 对象中所有的值,并以插入Map 对象的顺序排列。

Map.prototype[Symbol.iterator]()

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

示例

使用 Map 对象

js
const myMap = new Map();const keyString = "a string";const keyObj = {};const keyFunc = function () {};// 添加键myMap.set(keyString, "和键'a string'关联的值");myMap.set(keyObj, "和键 keyObj 关联的值");myMap.set(keyFunc, "和键 keyFunc 关联的值");console.log(myMap.size); // 3// 读取值console.log(myMap.get(keyString)); // "和键'a string'关联的值"console.log(myMap.get(keyObj)); // "和键 keyObj 关联的值"console.log(myMap.get(keyFunc)); // "和键 keyFunc 关联的值"console.log(myMap.get("a string")); // "和键'a string'关联的值",因为 keyString === 'a string'console.log(myMap.get({})); // undefined,因为 keyObj !== {}console.log(myMap.get(function () {})); // undefined,因为 keyFunc !== function () {}

将 NaN 作为 Map 的键

NaN 也可以作为键。虽然NaN 与任何值甚至与自己都不相等(NaN !== NaN 返回 true),但是因为所有的NaN 的值都是无法区分的,所以下面的例子成立:

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

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

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 = onefor (const key of myMap.keys()) {  console.log(key);}// 0// 1for (const value of myMap.values()) {  console.log(value);}// zero// onefor (const [key, value] of myMap.entries()) {  console.log(`${key} = ${value}`);}// 0 = zero// 1 = one

使用 forEach() 迭代 Map

Map 也可以通过forEach() 方法迭代:

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

Map 与数组对象的关系

js
const kvArray = [  ["key1", "value1"],  ["key2", "value2"],];// 使用常规的 Map 构造函数可以将一个二维的键值对数组转换成一个 Map 对象const myMap = new Map(kvArray);console.log(myMap.get("key1")); // "value1"// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维的键值对数组console.log(Array.from(myMap)); // 输出和 kvArray 相同的数组// 更简洁的方法来做如上同样的事情,使用展开运算符console.log([...myMap]);// 或者在键或者值的迭代器上使用 Array.from,进而得到只含有键或者值的数组console.log(Array.from(myMap.keys())); // 输出 ["key1", "key2"]

复制或合并 Maps

Map 能像数组一样被复制:

js
const original = new Map([[1, "one"]]);const clone = new Map(original);console.log(clone.get(1)); // oneconsole.log(original === clone); // false. 浅比较 不为同一个对象的引用

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

Map 对象间可以进行合并,但是会保持键的唯一性。

js
const first = new Map([  [1, "one"],  [2, "two"],  [3, "three"],]);const second = new Map([  [1, "uno"],  [2, "dos"],]);// 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的。// 展开语法本质上是将 Map 对象转换成数组。const merged = new Map([...first, ...second]);console.log(merged.get(1)); // unoconsole.log(merged.get(2)); // dosconsole.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"],]);// Map 对象同数组进行合并时,如果有重复的键值,则后面的会覆盖前面的。const merged = new Map([...first, ...second, [1, "eins"]]);console.log(merged.get(1)); // einsconsole.log(merged.get(2)); // dosconsole.log(merged.get(3)); // three

规范

Specification
ECMAScript® 2026 Language Specification
# sec-map-objects

浏览器兼容性

参见

Help improve MDN

Learn how to contribute

This page was last modified on byMDN contributors.


[8]ページ先頭

©2009-2025 Movatter.jp