ES6中的Maps和Objects,何时使用?

93

参考:MDN Maps

当键名是在运行时才知道,而且所有键都是相同类型且所有值也是相同类型时,使用 Map 而不是 Object。

当逻辑要操作单个元素时,使用对象。

问题:

什么是使用 Map 的适当示例?特别地,“什么情况下键名直到运行时才知道?”

var myMap = new Map();

var keyObj = {},
    keyFunc = function () { return 'hey'},
    keyString = "a string";

// 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.get(keyFunc));

8
您可以使用Map来关联一些数据与DOM元素,而不仅限于使用对象中的ID作为key值。通过将元素本身作为Map的key,您就不必担心元素是否具有ID或任何其他唯一标识符,只需要使用对象引用即可实现关联。 - RobG
2
@RobG 只是一个小的补充:在这种情况下,可能会有用的是WeakMap - zerkms
2
我认为这建议使用对象作为记录,而使用映射来进行其他类型的映射。在这里,记录指的是具有固定字段集的数据结构,例如用户对象,其具有名称和ID字段。 - Felix Kling
2
当我阅读那个 MDN 页面时,用例的项目列表比你引用的段落更有帮助。特别是与你标题中提出的问题相关的部分。 - CodingIntrigue
可能是JavaScript中的Map vs Object重复问题。 - Dan Dascalescu
显示剩余3条评论
6个回答

58

什么是使用地图而不是对象的适用示例?

我认为你已经给出了一个很好的例子:当你将对象(包括函数对象)用作键时,至少需要使用Map

具体而言,“何时会在运行时才知道键?”

每当它们在编译时未知时。简而言之,当您需要键-值集合时,应始终使用Map。需要集合的一个很好的指标是从集合动态添加和删除值,特别是当您事先不知道这些值时(例如,它们从数据库中读取,由用户输入等)。

相反,当您编写代码时知道对象具有哪些属性及其数量时 - 当它们的形状是静态的时候,应该使用对象。正如@Felix所说:当您需要记录时。需要这样做的一个很好的指标是字段具有不同类型,并且您从不需要使用方括号表示法(或者期望其中有一组有限的属性名称)。


2
或者从另一个角度来看:每当您需要在数据级别(例如 for..of)而不是程序级别(例如 for..in)上迭代对象的属性时,请使用 Map。有关此术语的更多信息,请参见此响应 - user6445533
我还会添加注释:每当您不知道您的键将是什么类型,并且您不希望字符串作为键数据类型时,请使用map。https://dev59.com/DlwY5IYBdhLWcg3weXkb - Carmine Tambascia

28
我认为使用ES2015的Map只有两个原因还在使用普通对象:
  • 您根本不想迭代对象类型的属性
  • 或者您确实这样做了,但属性顺序并不重要,并且在迭代时可以将程序与数据级别区分开来

何时属性顺序不重要?

  • 如果您只有一个值和一些应明确与其关联的函数(例如Promise - 它是未来值的代理 - 和then/catch
  • 如果您具有类似结构/记录的数据结构,其中包含在“编译时”已知的静态属性集(通常结构/记录不可迭代)

在所有其他情况下,您可能考虑使用Map,因为它保留属性顺序并将程序(分配给Map对象的所有属性)与数据级别(Map本身中的所有条目)分开。

Map的缺点是什么?

  • 你失去了简洁的对象字面量语法
  • 你需要自定义替换程序来处理JSON.stringify
  • 你失去了解构,但它在处理静态数据结构时更有用

这一行让我非常担心:“它可能比纯粹的类似哈希表的对象慢”。我曾考虑将所有的对象都替换为映射以提高性能。但你说它更慢... - mesqueeb
2
你说得对。Map 可能更快,因为它纯粹基于哈希,而 Object 则稍微复杂一些。谢谢! - user6445533

11
当键直到运行时才知道,且所有键都是相同类型且所有值都是相同类型时,请使用映射而不是对象。
我不知道为什么有人会写出如此明显错误的内容。我必须说,这些天人们在MDN上发现了越来越多错误和/或可疑的内容。
该句子中没有任何正确的地方。使用映射的主要原因是当您想要对象值键时。认为值应该是相同类型的想法是荒谬的 - 尽管它们可能是相同类型的。同样荒谬的是认为在键直到运行时才知道时就不应该使用对象。

2
我不明白这个想法有什么荒谬的地方?当你需要集合时,它们通常是严格类型化的(当然可能会有例外)。此外,我认为不再使用对象作为集合(当Map可用时)是一个好建议。 - Bergi
说集合通常是严格类型的,这在逻辑上与说某个严格类型的东西应该是一个集合是不同的。 - user663031
2
是的,我认为这有点奇怪,但想法还不错。我猜他们认为“键在运行时未知”已经构成了一个集合。有更好的措辞吗? - Bergi
1
同意。MDN有非常好的文档,但他们应该专注于记录API文档,而不是试图给出编程建议。 - AlexG

6
对象(Object)和 Map 在某些方面相似,都可以将键映射到值,获取这些值,删除键以及检测是否存储了某个键。因此,在历史上,由于没有内置的替代方案,对象被用作 Map。然而,有一些重要的区别使得在某些情况下使用 Map 更可取:
  • 对象的键必须是字符串(String)或符号(Symbol),而 Map 的键可以是任何值,包括函数、对象和任何基本类型。
  • Map 中的键是有序的,而添加到对象中的键则不是。因此,在迭代时,Map 对象会按插入顺序返回键。
  • 你可以轻松地通过 size 属性获取 Map 的大小,而必须手动确定对象中的属性数量。
  • Map 是可迭代的,因此可以直接进行迭代,而迭代对象需要以某种方式获取其键并对其进行迭代。
  • 对象具有原型,因此映射中存在默认键,如果不小心,这些键可能与您的键发生冲突。从 ES5 开始,可以通过使用 map = Object.create(null) 来绕过此问题,但这很少被使用。
  • 在频繁添加和删除键值对的情况下,Map 可能表现更佳。

MDN


5

MapObject之间的一个区别是:

Map可以使用复杂数据类型作为其键,例如:

const fn = function() {}
const m = new Map([[document.body, 'stackoverflow'], [fn, 'redis']]);

m.get(document.body) // 'stackoverflow'
m.get(fn) //'redis'

注意:对于复杂数据类型,如果您想获取值,则必须传递与键相同的引用。

Object 只接受简单数据类型(numberstring)作为其键。

const a = {};
a[document.body] = 'stackoverflow';

console.log(a) //{[object HTMLBodyElement]: "stackoverflow"}

3
这个问题是的重复,但在它被关闭之前,这里有我在那边的答案
除了其他答案外,我发现地图比对象更难操作且冗长。
obj[key] += x
// vs.
map.set(map.get(key) + x)

这很重要,因为较短的代码更易于阅读、更直接表达,并且更好地保留在程序员的头脑中
另一个方面:由于set()返回映射而不是值,因此无法链式赋值。
foo = obj[key] = x;  // Does what you expect
foo = map.set(key, x)  // foo !== x; foo === map

调试地图也更加痛苦。在下面,你实际上看不到地图中有哪些键。你需要编写代码来完成这个任务。

Good luck evaluating a Map Iterator

对象可以被任何IDE评估:

WebStorm evaluating an object


7
这并没有回答 "什么是使用地图而不是对象的适用示例?" 这个问题。 - Bergi
6
请不要在多个地方发布相同的回答。不,这个问题不是重复的。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接