JavaScript 中的地图与对象
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18541940/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Map vs Object in JavaScript
提问by Dave
I just discovered chromestatus.com and, after losing several hours of my day, found this feature entry:
我刚刚发现了 chromestatus.com,在我一天中失去了几个小时之后,发现了这个功能条目:
Map: Map objects are simple key/value maps.
Map:Map 对象是简单的键/值映射。
That confused me. Regular JavaScript objects are dictionaries, so how is a Map
different from a dictionary? Conceptually, they're identical (according to What is the difference between a Map and a Dictionary?)
这让我很困惑。常规 JavaScript 对象是字典,那么 aMap
与字典有何不同?从概念上讲,它们是相同的(根据 Map 和 Dictionary 之间的区别是什么?)
The documentation chromestatus references doesn't help either:
文档 chromestatus 参考也无济于事:
Map objects are collections of key/value pairs where both the keys and values may be arbitrary ECMAScript language values. A distinct key value may only occur in one key/value pair within the Map's collection. Distinct key values as discriminated using the a comparision algorithm that is selected when the Map is created.
A Map object can iterate its elements in insertion order. Map object must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements in the collection. The data structures used in this Map objects specification is only intended to describe the required observable semantics of Map objects. It is not intended to be a viable implementation model.
Map 对象是键/值对的集合,其中键和值都可以是任意的 ECMAScript 语言值。不同的键值只能出现在 Map 集合中的一个键/值对中。使用创建 Map 时选择的比较算法区分不同的键值。
Map 对象可以按插入顺序迭代其元素。Map 对象必须使用哈希表或其他机制来实现,平均而言,这些机制提供的访问时间与集合中的元素数量是次线性的。此 Map 对象规范中使用的数据结构仅用于描述 Map 对象所需的可观察语义。它不是一个可行的实施模型。
…still sounds like an object to me, so clearly I've missed something.
......对我来说仍然听起来像一个对象,很明显我错过了一些东西。
Why is JavaScript gaining a (well-supported) Map
object? What does it do?
为什么 JavaScript 会获得(得到良好支持的)Map
对象?它有什么作用?
采纳答案by Dave
According to mozilla:
根据 Mozilla 的说法:
A Map object can iterate its elements in insertion order - a for..of loop will return an array of [key, value] for each iteration.
Map 对象可以按插入顺序迭代其元素 - for..of 循环将为每次迭代返回一个 [key, value] 数组。
and
和
Objects are similar to Maps in that both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key. Because of this, Objects have been used as Maps historically; however, there are important differences between Objects and Maps that make using a Map better.
An Object has a prototype, so there are default keys in the map. However, this can be bypassed using map = Object.create(null). The keys of an Object are Strings, where they can be any value for a Map. You can get the size of a Map easily while you have to manually keep track of size for an Object.
Use maps over objects when keys are unknown until run time, and when all keys are the same type and all values are the same type.
Use objects when there is logic that operates on individual elements.
对象与 Maps 的相似之处在于,它们都允许您将键设置为值、检索这些值、删除键以及检测某个键是否存储了某些内容。正因为如此,对象在历史上被用作地图;然而,对象和地图之间有重要的区别,这使得使用地图更好。
一个对象有一个原型,所以地图中有默认的键。但是,这可以使用 map = Object.create(null) 绕过。对象的键是字符串,它们可以是 Map 的任何值。您可以轻松获得地图的大小,而您必须手动跟踪对象的大小。
当键在运行时之前未知,并且所有键的类型和所有值的类型都相同时,请使用对象映射。
当存在对单个元素进行操作的逻辑时使用对象。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
The iterability-in-order is a feature that has long been wanted by developers, in part because it ensures the same performance in all browsers. So to me that's a big one.
顺序迭代是开发人员长期以来一直想要的功能,部分原因是它确保在所有浏览器中具有相同的性能。所以对我来说这是一个很大的问题。
The myMap.has(key)
method will be especially handy, and also the myMap.size
property.
该myMap.has(key)
方法将特别方便,还有myMap.size
财产。
回答by jgmjgm
The key difference is that Objects only support string keys where as Maps support more or less any key type.
关键的区别在于对象只支持字符串键,而地图或多或少支持任何键类型。
If I do obj[123] = true
and then Object.keys(obj)
then I will get ["123"]
rather than [123]
. A Map would preserve the type of the key and return [123]
which is great. Maps also allow you to use Objects as keys. Traditionally to do this you would have to give objects some kind of unique identifier to hash them (I don't think I've ever seen anything like getObjectId
in JS as part of the standard). Maps also guarantee preservation of order so are all round better for preservation and can sometimes save you needing to do a few sorts.
如果我这样做obj[123] = true
,那么Object.keys(obj)
我会得到["123"]
而不是[123]
. Map 将保留键的类型并返回[123]
,这很好。地图还允许您使用对象作为键。传统上,要做到这一点,您必须为对象提供某种唯一标识符来对它们进行哈希处理(我认为我从未getObjectId
在 JS 中看到任何类似的东西作为标准的一部分)。地图还可以保证顺序的保存,因此在保存方面更全面,有时可以节省您需要做的一些事情。
Between maps and objects in practice there are several pros and cons. Objects gain both advantages and disadvantages being very tightly integrated into the core of JavaScript which sets them apart from significantly Map beyond the difference in key support.
在实践中,地图和对象之间有几个优点和缺点。对象的优点和缺点都非常紧密地集成到 JavaScript 的核心中,这使它们与 Map 的显着区别超出了关键支持的差异。
An immediate advantage is that you have syntactical support for Objects making it easy to access elements. You also have direct support for it with JSON. When used as a hash it's annoying to get an object without any properties at all. By default if you want to use Objects as a hash table they will be polluted and you will often have to call hasOwnProperty
on them when accessing properties. You can see here how by default Objects are polluted and how to create hopefully unpolluted objects for use as hashes:
一个直接的优势是您对对象具有语法支持,从而可以轻松访问元素。您还可以通过 JSON 直接支持它。当用作散列时,获得一个完全没有任何属性的对象是很烦人的。默认情况下,如果您想将对象用作哈希表,它们将被污染,并且您hasOwnProperty
在访问属性时通常必须调用它们。您可以在此处查看默认情况下对象如何被污染以及如何创建希望未受污染的对象以用作散列:
({}).toString
toString() { [native code] }
JSON.parse('{}').toString
toString() { [native code] }
(Object.create(null)).toString
undefined
JSON.parse('{}', (k,v) => (typeof v === 'object' && Object.setPrototypeOf(v, null) ,v)).toString
undefined
Pollution on objects is not only something that makes code more annoying, slower, etc but can also have potential consequences for security.
对象上的污染不仅会使代码更烦人、更慢等,而且还会对安全性产生潜在的影响。
Objects are not pure hash tables but are trying to do more. You have headaches like hasOwnProperty
, not being able to get the length easily (Object.keys(obj).length
) and so on. Objects are not meant to purely be used as hash maps but as dynamic extensible Objects as well and so when you use them as pure hash tables problems arise.
对象不是纯粹的哈希表,而是试图做更多的事情。您会头痛,例如hasOwnProperty
无法轻松获得长度 ( Object.keys(obj).length
) 等。对象并不是纯粹用作散列映射,而是用作动态可扩展对象,因此当您将它们用作纯散列表时会出现问题。
Comparison/List of various common operations:
各种常用操作对比/列表:
Object:
var o = {};
var o = Object.create(null);
o.key = 1;
o.key += 10;
for(let k in o) o[k]++;
var sum = 0;
for(let v of Object.values(m)) sum += v;
if('key' in o);
if(o.hasOwnProperty('key'));
delete(o.key);
Object.keys(o).length
Map:
var m = new Map();
m.set('key', 1);
m.set('key', m.get('key') + 10);
m.foreach((k, v) => m.set(k, m.get(k) + 1));
for(let k of m.keys()) m.set(k, m.get(k) + 1);
var sum = 0;
for(let v of m.values()) sum += v;
if(m.has('key'));
m.delete('key');
m.size();
There are a few other options, approaches, methodologies, etc with varying ups and downs (performance, terse, portable, extendable, etc). Objects are a bit strange being core to the language so you have a lot of static methods for working with them.
还有一些其他选项、方法、方法等,它们有不同的起伏(性能、简洁、便携、可扩展等)。对象作为语言的核心有点奇怪,因此您有很多静态方法可以使用它们。
Besides the advantage of Maps preserving key types as well as being able to support things like objects as keys they are isolated from the side effects that objects much have. A Map is a pure hash, there's no confusion about trying to be an object at the same time. Maps can also be easily extended with proxy functions. Object's currently have a Proxy class however performance and memory usage is grim, in fact creating your own proxy that looks like Map for Objects currently performs better than Proxy.
除了 Maps 保留键类型的优点以及能够支持对象作为键之类的东西之外,它们还与对象具有的副作用隔离开来。Map 是一个纯粹的散列,尝试同时成为一个对象没有任何混淆。地图也可以通过代理功能轻松扩展。Object 目前有一个 Proxy 类,但是性能和内存使用情况很糟糕,事实上,创建自己的代理,看起来像 Map for Objects 目前比 Proxy 性能更好。
A substantial disadvantage for Maps is that they are not supported with JSON directly. Parsing is possible but has several hangups:
Maps 的一个重大缺点是它们不直接支持 JSON。解析是可能的,但有几个挂断:
JSON.parse(str, (k,v) => {
if(typeof v !== 'object') return v;
let m = new Map();
for(k in v) m.set(k, v[k]);
return m;
});
The above will introduce a serious performance hit and will also not support any string keys. JSON encoding is even more difficult and problematic (this is one of many approaches):
以上将引入严重的性能损失,并且也不支持任何字符串键。JSON 编码更加困难和成问题(这是许多方法之一):
// An alternative to this it to use a replacer in JSON.stringify.
Map.prototype.toJSON = function() {
return JSON.stringify({
keys: Array.from(this.keys()),
values: Array.from(this.values())
});
};
This is not so bad if you're purely using Maps but will have problems when you are mixing types or using non-scalar values as keys (not that JSON is perfect with that kind of issue as it is, IE circular object reference). I haven't tested it but chances are that it will severely hurt performance compared to stringify.
如果您纯粹使用 Maps,这还算不错,但是当您混合类型或使用非标量值作为键时会出现问题(并不是 JSON 在处理这类问题时是完美的,IE 循环对象引用)。我还没有测试过它,但与 stringify 相比,它可能会严重损害性能。
Other scripting languages often don't have such problems as they have explicit non-scalar types for Map, Object and Array. Web development is often a pain with non-scalar types where you have to deal with things like PHP merges Array/Map with Object using A/M for properties and JS merges Map/Object with Array extending M/O. Merging complex types is the devil's bane of high level scripting languages.
其他脚本语言通常没有这样的问题,因为它们具有显式的 Map、Object 和 Array 非标量类型。对于非标量类型,Web 开发通常很痛苦,您必须处理诸如 PHP 将 Array/Map 与 Object 合并使用 A/M 作为属性以及 JS 将 Map/Object 与 Array 扩展 M/O 合并之类的事情。合并复杂类型是高级脚本语言的恶魔。
So far these are largely issues around implementation but performance for basic operations is important as well. Performance is also complex because it depends on engine and usage. Take my tests with a grain of salt as I cannot rule out any mistake (I have to rush this). You should also run your own tests to confirm as mine examine only very specific simple scenarios to give a rough indication only. According to tests in Chrome for very large objects/maps the performance for objects is worse because of delete which is apparently somehow proportionate to the number of keys rather than O(1):
到目前为止,这些主要是围绕实现的问题,但基本操作的性能也很重要。性能也很复杂,因为它取决于引擎和使用情况。对我的测试持保留态度,因为我不能排除任何错误(我必须赶这个)。您还应该运行自己的测试来确认,因为我只检查了非常具体的简单场景,以便仅给出粗略的指示。根据 Chrome 中对非常大的对象/地图的测试,对象的性能更差,因为删除显然与键的数量而不是 O(1) 成正比:
Object Set Took: 146
Object Update Took: 7
Object Get Took: 4
Object Delete Took: 8239
Map Set Took: 80
Map Update Took: 51
Map Get Took: 40
Map Delete Took: 2
Chrome clearly has a strong advantage with getting and updating but the delete performance is horrific. Maps use a tiny amount more memory in this case (overhead) but with only one Object/Map being tested with millions of keys the impact of overhead for maps is not expressed well. With memory management objects also do seem to free earlier if I am reading the profile correctly which might be one benefit in favor of objects.
Chrome 显然在获取和更新方面具有很强的优势,但删除性能却很糟糕。在这种情况下,地图使用了少量更多的内存(开销),但只有一个对象/地图使用数百万个键进行测试,地图开销的影响没有得到很好的表达。如果我正确读取配置文件,内存管理对象似乎也会更早地释放,这可能是有利于对象的一个好处。
In Firefox for this particular benchmark it is a different story:
在 Firefox 中,对于这个特定的基准测试,情况就不同了:
Object Set Took: 435
Object Update Took: 126
Object Get Took: 50
Object Delete Took: 2
Map Set Took: 63
Map Update Took: 59
Map Get Took: 33
Map Delete Took: 1
I should immediately point out that in this particular benchmark deleting from objects in Firefox is not causing any problems, however in other benchmarks it has caused problems especially when there are many keys just as in Chrome. Maps are clearly superior in Firefox for large collections.
我应该立即指出,在这个特定的基准测试中,从 Firefox 中的对象中删除不会导致任何问题,但是在其他基准测试中它会导致问题,尤其是当有很多键时,就像在 Chrome 中一样。对于大型集合,Firefox 中的地图显然更胜一筹。
However this is not the end of the story, what about many small objects or maps? I have done a quick benchmark of this but not an exhaustive one (setting/getting) of which performs best with a small number of keys in the above operations. This test is more about memory and initialization.
然而这还不是故事的结局,还有很多小物件或地图呢?我已经对此进行了快速基准测试,但不是详尽的基准测试(设置/获取),其中在上述操作中使用少量键时性能最佳。这个测试更多的是关于内存和初始化。
Map Create: 69 // new Map
Object Create: 34 // {}
Again these figures vary but basically Object has a good lead. In some cases the lead for Objects over maps is extreme (~10 times better) but on average it was around 2-3 times better. It seems extreme performance spikes can work both ways. I only tested this in Chrome and creation to profile memory usage and overhead. I was quite surprised to see that in Chrome it appears that Maps with one key use around 30 times more memory than Objects with one key.
同样,这些数字各不相同,但基本上 Object 领先。在某些情况下,对象优于地图的领先优势是极端的(高出约 10 倍),但平均而言高出约 2-3 倍。似乎极端的性能峰值可以双向工作。我只在 Chrome 和创建中对此进行了测试,以分析内存使用情况和开销。我很惊讶地看到,在 Chrome 中,一键式地图使用的内存似乎是一键式对象的 30 倍左右。
For testing many small objects with all the above operations (4 keys):
使用上述所有操作(4个键)测试许多小对象:
Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139
In terms of memory allocation these behaved the same in terms of freeing/GC but Map used 5 times more memory. This test used 4 keys where as in the last test I only set one key so this would explain the reduction in memory overhead. I ran this test a few times and Map/Object are more or less neck and neck overall for Chrome in terms of overall speed. In Firefox for small Objects there is a definite performance advantage over maps overall.
在内存分配方面,它们在释放/GC 方面的行为相同,但 Map 使用了 5 倍的内存。这个测试使用了 4 个键,而在上一个测试中我只设置了一个键,所以这可以解释内存开销的减少。我运行了几次这个测试,就整体速度而言,地图/对象或多或少与 Chrome 并驾齐驱。在 Firefox for small Objects 中,整体上比地图有明显的性能优势。
This of course doesn't include the individual options which could vary wildly. I would not advice micro-optimizing with these figures. What you can get out of this is that as a rule of thumb, consider Maps more strongly for very large key value stores and objects for small key value stores.
这当然不包括可能变化很大的个别选项。我不建议对这些数字进行微观优化。您可以从中得到的是,根据经验,对于非常大的键值存储和对象对小键值存储更强烈地考虑映射。
Beyond that the best strategy with these two it to implement it and just make it work first. When profiling it is important to keep in mind that sometimes things that you wouldn't think would be slow when looking at them can be incredibly slow because of engine quirks as seen with the object key deletion case.
除此之外,这两个的最佳策略是实施它并使其首先起作用。在分析时,重要的是要记住,有时在查看它们时您认为不会很慢的事情可能会非常慢,因为在对象键删除案例中看到的引擎怪癖。
回答by jgmjgm
I don't think the following points have been mentioned in the answers so far, and I thought they'd be worth mentioning.
我认为到目前为止答案中没有提到以下几点,我认为它们值得一提。
Maps can be bigger
地图可以更大
In chrome I can get 16.7million key/value pairs with Map
vs. 11.1million with a regular object. Almost exactly 50% more pairs with a Map
. They both take up about 2GB of memory before they crash, and so I think may be to do with memory limiting by chrome (Edit: Yep, try filling 2 Maps
and you only get to 8.3 million pairs each before it crashes). You can test it yourself with this code (run them separately and not at the same time, obviously):
在 chrome 中,我可以获得1670万个键/值对,Map
而普通对象则为1110万个。带有Map
. 它们在崩溃之前都占用了大约 2GB 的内存,所以我认为可能与 chrome 的内存限制有关(编辑:是的,尝试填充 2Maps
并且在崩溃之前你只能得到 830 万对)。您可以使用此代码自行测试(显然,单独运行它们而不是同时运行它们):
var m = new Map();
var i = 0;
while(1) {
m.set(((10**30)*Math.random()).toString(36), ((10**30)*Math.random()).toString(36));
i++;
if(i%1000 === 0) { console.log(i/1000,"thousand") }
}
// versus:
var m = {};
var i = 0;
while(1) {
m[((10**30)*Math.random()).toString(36)] = ((10**30)*Math.random()).toString(36);
i++;
if(i%1000 === 0) { console.log(i/1000,"thousand") }
}
Objects have some properties/keys already
对象已经有一些属性/键
This one has tripped me up before. Regular objects have toString
, constructor
, valueOf
, hasOwnProperty
, isPrototypeOf
and a bunch of other pre-existing properties. This may not be a big problem for most use cases, but it has caused problems for me before.
这个以前让我绊倒了。有规则物体的有toString
,constructor
,valueOf
,hasOwnProperty
,isPrototypeOf
和一堆其他已存在的属性。对于大多数用例来说,这可能不是一个大问题,但它之前给我带来了问题。
Maps can be slower:
地图可能会更慢:
Due to the .get
function call overhead and lack of internal optimisation, Map can be considerably slowerthan a plain old JavaScript object for some tasks.
回答by Mani Gandham
Objects can behave like dictionaries because Javascript is dynamically typed, allowing you to add or remove properties on an object any time.
对象可以像字典一样运行,因为 Javascript 是动态类型的,允许您随时添加或删除对象的属性。
But the new Map()
functionality is much better because:
但新Map()
功能要好得多,因为:
- It provides
get
,set
,has
, anddelete
methods. - Accepts any type for the keys instead of just strings.
- Provides an iterator for easy
for-of
usage and maintains order of results. - Doesn't have edge cases with prototypes and other properties showing up during iteration or copying.
- It supports millions of items.
- Is very fast and keeps getting faster as javascript engines get better.
- 它提供了
get
,set
,has
,和delete
方法。 - 接受任何类型的键,而不仅仅是字符串。
- 提供迭代器以方便
for-of
使用并维护结果顺序。 - 在迭代或复制期间没有出现原型和其他属性的边缘情况。
- 它支持数百万个项目。
- 非常快,并且随着 javascript 引擎变得更好而变得越来越快。
99% of the time you should just use a Map()
. However if you're only using string-based keys and need maximum read performance, then objects can be a better choice.
99% 的情况下,您应该只使用Map()
. 但是,如果您只使用基于字符串的键并且需要最大的读取性能,那么对象可能是更好的选择。
The detail is that (almost all) javascript engines compile objects down to C++ classes in the background. These types are cached and reused by their "outline", so when you make a new object with the same exact properties the engine will reuse an existing background class. The access path for properties on these classes is very optimized and much faster than the lookup of a Map()
.
细节是(几乎所有)javascript 引擎在后台将对象编译为 C++ 类。这些类型被它们的“大纲”缓存和重用,所以当你创建一个具有完全相同属性的新对象时,引擎将重用现有的背景类。这些类上的属性的访问路径非常优化,并且比查找Map()
.
Adding or removing a property causes the cached backing class to be re-compiled which is why using an object as a dictionary with lots of key additions and deletions is very slow, but reads and assignment of existing keys without changing the object are very fast.
添加或删除属性会导致重新编译缓存的支持类,这就是为什么将对象用作具有大量键添加和删除的字典非常慢,但在不更改对象的情况下读取和分配现有键非常快。
So if you have a write-once read-heavy workload with string keys then use an object
as a specialized high-performance dictionary, but for everything else use a Map()
.
所以,如果你有一个写一次具有字符串键读繁重的工作,然后使用object
一个专门的高性能的字典,但对于一切使用Map()
。
回答by Willem van der Veen
Summary:
概括:
Object
: A data structure in which data is stored as key value pairs. In an object the key has to be a number, string, or symbol. The value can be anything so also other objects, functions etc. A object is an non ordereddata structure, i.e. the sequence of insertion of key value pairs is not rememberedES6 Map
: A data structure in which data is stored as key value pairs. In which a unique key maps to a value. Both the key and the value can be in any data type. A map is a iterable data structure, this means that the sequence of insertion is remembered and that we can access the elements in e.g. afor..of
loop
Object
:一种数据结构,其中数据以键值对的形式存储。在对象中,键必须是数字、字符串或符号。值可以是任何其他对象、函数等。对象是无序的数据结构,即不会记住键值对的插入顺序ES6 Map
:一种数据结构,其中数据以键值对的形式存储。其中唯一键映射到值。键和值都可以是任何数据类型。map 是一种可迭代的数据结构,这意味着插入的顺序会被记住,我们可以在例如for..of
循环中访问元素
Key differences:
主要区别:
A
Map
is ordered and iterable, whereas a objects is not ordered and not iterableWe can put any type of data as a
Map
key, whereas objects can only have a number, string, or symbol as a key.A
Map
inherits fromMap.prototype
. This offers all sorts of utility functions and properties which makes working withMap
objects a lot easier.
A
Map
是有序且可迭代的,而对象是无序且不可迭代的我们可以将任何类型的数据作为
Map
键,而对象只能以数字、字符串或符号作为键。A
Map
继承自Map.prototype
. 这提供了各种实用功能和属性,使处理Map
对象变得更加容易。
Example:
例子:
object:
目的:
let obj = {};
// adding properties to a object
obj.prop1 = 1;
obj[2] = 2;
// getting nr of properties of the object
console.log(Object.keys(obj).length)
// deleting a property
delete obj[2]
console.log(obj)
Map:
地图:
const myMap = new Map();
const keyString = 'a string',
keyObj = {},
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 () {}
回答by Dan Dascalescu
In addition to the other answers, I've found that Maps are more unwieldy and verbose to operate with than objects.
除了其他答案之外,我发现 Maps 比对象更笨拙和冗长。
obj[key] += x
// vs.
map.set(map.get(key) + x)
This is important, because shorter code is faster to read, more directly expressive, and better kept in the programmer's head.
这很重要,因为更短的代码读起来更快,表达更直接,并且更好地保留在程序员的脑海中。
Another aspect: because set() returns the map, not the value, it's impossible to chain assignments.
另一个方面:因为 set() 返回的是映射,而不是值,所以不可能链式赋值。
foo = obj[key] = x; // Does what you expect
foo = map.set(key, x) // foo !== x; foo === map
Debugging maps is also more painful. Below, you can't actually see what keys are in the map. You'd have to write code to do that.
调试地图也比较痛苦。在下面,您实际上无法看到地图中的键。您必须编写代码才能做到这一点。
Objects can be evaluated by any IDE:
任何 IDE 都可以评估对象:
回答by Oriol
Additionally to being iterable in a well-defined order, and the ability to use arbitrary values as keys (except -0
), maps can be useful because of the following reasons:
除了以明确定义的顺序可迭代,以及使用任意值作为键(除了-0
)的能力之外,由于以下原因,映射可能很有用:
The spec enforces map operations to be sublinear on average.
Any non-stupid implementation of object will use a hash table or similar, so property lookups will probably be constant on average. Then objects could be even faster than maps. But that is not required by the spec.
Objects can have nasty unexpected behaviors.
For example, let's say you didn't set any
foo
property to a newly created objectobj
, so you expectobj.foo
to return undefined. Butfoo
could be built-in property inherited fromObject.prototype
. Or you attempt to createobj.foo
by using an assignment, but some setter inObject.prototype
runs instead of storing your value.Maps prevent these kind of things. Well, unless some script messes up with
Map.prototype
. AndObject.create(null)
would work too, but then you lose the simple object initializer syntax.
该规范强制地图操作平均是次线性的。
对象的任何非愚蠢实现都将使用哈希表或类似表,因此属性查找可能平均保持不变。那么对象可能比地图更快。但这不是规范所要求的。
对象可能有令人讨厌的意外行为。
例如,假设您没有
foo
为新创建的 object设置任何属性obj
,因此您希望obj.foo
返回 undefined。但foo
可以是继承自Object.prototype
. 或者您尝试obj.foo
通过使用赋值来创建,但某些 setter 正在Object.prototype
运行而不是存储您的值。地图可以防止这些事情。好吧,除非某些脚本与
Map.prototype
. 并且Object.create(null)
也可以工作,但是您会丢失简单的对象初始值设定项语法。
回答by djb7861
When to use Maps instead of plain JavaScript Objects ?
何时使用 Maps 而不是纯 JavaScript 对象?
The plain JavaScript Object { key: 'value' } holds structured data. But plain JS object has its limitations :
纯 JavaScript 对象 { key: 'value' } 保存结构化数据。但是普通的 JS 对象有其局限性:
Only strings and symbols can be used as keys of Objects. If we use any other things say, numbers as keys of an object then during accessing those keys we will see those keys will be converted into strings implicitly causing us to lose consistency of types. const names= {1: 'one', 2: 'two'}; Object.keys(names); // ['1', '2']
There are chances of accidentally overwriting inherited properties from prototypes by writing JS identifiers as key names of an object (e.g. toString, constructor etc.)
Another object cannot be used as key of an object, so no extra information can be written for an object by writing that object as key of another object and value of that another object will contain the extra information
Objects are not iterators
The size of an object cannot be determined directly
只有字符串和符号可以用作对象的键。如果我们使用任何其他东西,比如数字作为对象的键,那么在访问这些键的过程中,我们将看到这些键将被隐式转换为字符串,导致我们失去类型的一致性。常量名称= {1:'一',2:'二'};Object.keys(names); // ['1', '2']
通过将 JS 标识符写入对象的键名(例如 toString、构造函数等),可能会意外覆盖从原型继承的属性。
另一个对象不能用作一个对象的键,因此不能通过将该对象写入另一个对象的键来为该对象写入额外信息,并且该另一个对象的值将包含额外信息
对象不是迭代器
无法直接确定物体的大小
These limitations of Objects are solved by Maps but we must consider Maps as complement for Objects instead of replacement. Basically Map is just array of arrays but we must pass that array of arrays to the Map object as argument with new keyword otherwise only for array of arrays the useful properties and methods of Map aren't available. And remember key-value pairs inside the array of arrays or the Map must be separated by commas only, no colons like in plain objects.
Objects 的这些限制是由 Maps 解决的,但我们必须将 Maps 视为 Objects 的补充而不是替代品。基本上 Map 只是数组的数组,但我们必须将该数组作为参数传递给 Map 对象,并使用 new 关键字,否则仅对于数组数组,Map 的有用属性和方法不可用。并记住数组数组或 Map 中的键值对只能用逗号分隔,不能像普通对象那样用冒号分隔。
3 tips to decide whether to use a Map or an Object :
决定使用 Map 还是 Object 的 3 个提示:
Use maps over objects when keys are unknown until run time because keys formed by user input or unknowingly can break the code which uses the object if those keys overwrite the inherited properties of the object, so map is safer in those cases. Also use maps when all keys are the same type and all maps are the same type.
Use maps if there is a need to store primitive values as keys.
Use objects if we need to operate on individual elements.
当键在运行时之前未知时,在对象上使用映射,因为如果这些键覆盖了对象的继承属性,由用户输入或在不知不觉中形成的键可能会破坏使用对象的代码,因此在这些情况下映射更安全。当所有键都是相同类型并且所有映射都是相同类型时,也可以使用映射。
如果需要将原始值存储为键,请使用映射。
如果我们需要对单个元素进行操作,请使用对象。
Benefits of using Maps are :
使用地图的好处是:
1. Map accepts any key type and preserves the type of key :
1. Map 接受任何键类型并保留键的类型:
We know that if the object's key is not a string or symbol then JS implicitly transforms it into a string. On the contrary, Map accepts any type of keys : string, number, boolean, symbol etc. and Map preserves the original key type. Here we will use number as key inside a Map and it will remain a number :
我们知道,如果对象的键不是字符串或符号,那么 JS 会隐式地将其转换为字符串。相反,Map 接受任何类型的键:字符串、数字、布尔值、符号等,而 Map 保留原始键类型。在这里,我们将使用 number 作为 Map 中的键,它仍然是一个数字:
const numbersMap= new Map();
numbersMap.set(1, 'one');
numbersMap.set(2, 'two');
const keysOfMap= [...numbersMap.keys()];
console.log(keysOfMap); // [1, 2]
Inside a Map we can even use an entire object as a key. There may be times when we want to store some object related data, without attaching this data inside the object itself so that we can work with lean objects but want to store some information about the object. In those cases we need to use Map so that we can make Object as key and related data of the object as value.
在 Map 中,我们甚至可以使用整个对象作为键。有时我们想要存储一些与对象相关的数据,而不是将这些数据附加到对象本身中,这样我们就可以处理精益对象,但想要存储一些关于对象的信息。在这些情况下,我们需要使用 Map 以便我们可以将对象作为键,将对象的相关数据作为值。
const foo= {name: foo};
const bar= {name: bar};
const kindOfMap= [[foo, 'Foo related data'], [bar, 'Bar related data']];
But the downside of this approach is the complexity of accessing the value by key, as we have to loop through the entire array to get the desired value.
但是这种方法的缺点是通过键访问值的复杂性,因为我们必须遍历整个数组才能获得所需的值。
function getBy Key(kindOfMap, key) {
for (const [k, v] of kindOfMap) {
if(key === k) {
return v;
}
}
return undefined;
}
getByKey(kindOfMap, foo); // 'Foo related data'
We can solve this problem of not getting direct access to the value by using a proper Map.
我们可以通过使用适当的 Map 来解决无法直接访问值的问题。
const foo= {name: 'foo'};
const bar= {name: 'bar'};
const myMap= new Map();
myMap.set(foo, 'Foo related data');
myMap.set(bar, 'Bar related data');
console.log(myMap.get(foo)); // 'Foo related data'
We could have done this using WeakMap, just have to write, const myMap= new WeakMap( ). The differences between Map and WeakMap are that WeakMap allows for garbage collection of keys (here objects) so it prevents memory leaks, WeakMap accepts only objects as keys, and WeakMap has reduced set of methods.
我们可以使用 WeakMap 来完成,只需编写 const myMap= new WeakMap()。Map 和 WeakMap 之间的区别在于 WeakMap 允许对键(这里是对象)进行垃圾收集,因此它可以防止内存泄漏,WeakMap 只接受对象作为键,而 WeakMap 减少了方法集。
2. Map has no restriction over key names :
2. Map 对键名没有限制:
For plain JS objects we can accidentally overwrite property inherited from the prototype and it can be dangerous. Here we will overwrite the toString( ) property of the actor object :
对于普通的 JS 对象,我们可能会意外覆盖从原型继承的属性,这可能很危险。这里我们将覆盖 actor 对象的 toString() 属性:
const actor= {
name: 'Harrison Ford',
toString: 'Actor: Harrison Ford'
};
Now let's define a fn isPlainObject( ) to determine if the supplied argument is a plain object and this fn uses toString( ) method to check it :
现在让我们定义一个 fn isPlainObject() 来确定提供的参数是否是一个普通对象,这个 fn 使用 toString() 方法来检查它:
function isPlainObject(value) {
return value.toString() === '[object Object]';
}
isPlainObject(actor); // TypeError : value.toString is not a function
// this is because inside actor object toString property is a string instead of inherited method from prototype
The Map does not have any restrictions on the key names, we can use key names like toString, constructor etc. Here although actorMap object has a property named toString but the method toString( ) inherited from prototype of actorMap object works perfectly.
Map 对键名没有任何限制,我们可以使用toString、constructor 等键名。这里虽然actorMap 对象有一个名为toString 的属性,但是从actorMap 对象原型继承的toString() 方法工作得很好。
const actorMap= new Map();
actorMap.set('name', 'Harrison Ford');
actorMap.set('toString', 'Actor: Harrison Ford');
function isMap(value) {
return value.toString() === '[object Map]';
}
console.log(isMap(actorMap)); // true
If we have a situation where user input creates keys then we must take those keys inside a Map instead of a plain object. This is because user may choose a custom field name like, toString, constructor etc. then such key names in a plain object can potentially break the code that later uses this object. So the right solution is to bind the user interface state to a map, there is no way to break the Map :
如果我们遇到用户输入创建键的情况,那么我们必须将这些键放入 Map 而不是普通对象。这是因为用户可以选择自定义字段名称,如 toString、构造函数等。那么普通对象中的此类键名可能会破坏以后使用该对象的代码。所以正确的解决方案是将用户界面状态绑定到一个地图,没有办法打破 Map :
const userCustomFieldsMap= new Map([['color', 'blue'], ['size', 'medium'], ['toString', 'A blue box']]);
3. Map is iterable :
3. 地图是可迭代的:
To iterate a plain object's properties we need Object.entries( ) or Object.keys( ). The Object.entries(plainObject) returns an array of key value pairs extracted from the object, we can then destructure those keys and values and can get normal keys and values output.
要迭代一个普通对象的属性,我们需要 Object.entries() 或 Object.keys()。Object.entries(plainObject) 返回一个从对象中提取的键值对数组,然后我们可以解构这些键和值,并可以得到正常的键和值输出。
const colorHex= {
'white': '#FFFFFF',
'black': '#000000'
}
for(const [color, hex] of Object.entries(colorHex)) {
console.log(color, hex);
}
//
'white' '#FFFFFF'
'black' '#000000'
As Maps are iterable that's why we do not need entries( ) methods to iterate over a Map and destructuring of key, value array can be done directly on the Map as inside a Map each element lives as an array of key value pairs separated by commas.
由于 Maps 是可迭代的,这就是为什么我们不需要 entry() 方法来迭代 Map 和解构键,值数组可以直接在 Map 上完成,因为在 Map 内部,每个元素都作为由逗号分隔的键值对数组而存在.
const colorHexMap= new Map();
colorHexMap.set('white', '#FFFFFF');
colorHexMap.set('black', '#000000');
for(const [color, hex] of colorHexMap) {
console.log(color, hex);
}
//'white' '#FFFFFF' 'black' '#000000'
Also map.keys( ) returns an iterator over keys and map.values( ) returns an iterator over values.
此外,map.keys() 返回键上的迭代器,map.values() 返回值上的迭代器。
4. We can easily know the size of a Map
4.我们可以很容易地知道一张地图的大小
We cannot directly determine the number of properties in a plain object. We need a helper fn like, Object.keys( ) which returns an array with keys of the object then using length property we can get the number of keys or the size of the plain object.
我们无法直接确定普通对象中的属性数量。我们需要一个像 Object.keys() 这样的助手 fn,它返回一个包含对象键的数组,然后使用 length 属性我们可以获得键的数量或普通对象的大小。
const exams= {'John Rambo': '80%', 'James Bond': '60%'};
const sizeOfObj= Object.keys(exams).length;
console.log(sizeOfObj); // 2
But in the case of Maps we can have direct access to the size of the Map using map.size property.
但是在 Maps 的情况下,我们可以使用 map.size 属性直接访问 Map 的大小。
const examsMap= new Map([['John Rambo', '80%'], ['James Bond', '60%']]);
console.log(examsMap.size);
回答by djb7861
These two tips can help you to decide whether to use a Map or an Object:
这两个技巧可以帮助您决定是使用 Map 还是 Object:
Use maps over objects when keys are unknown until run time, and when all keys are the same type and all values are the same type.
Use maps in case if there is a need to store primitive values as keys because object treats each key as a string either its a number value, boolean value or any other primitive value.
Use objects when there is logic that operates on individual elements.
当键在运行时之前未知,并且所有键的类型和所有值的类型都相同时,请使用对象映射。
如果需要将原始值存储为键,请使用映射,因为对象将每个键视为字符串,无论是数字值、布尔值还是任何其他原始值。
当存在对单个元素进行操作的逻辑时使用对象。
来源:https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections#Object_and_Map_compared
回答by nonopolarity
This is a short way for me to remember it: KOI
这是让我记住它的简短方式:KOI
- Keys. Object key is strings or symbols. Map keys can also be numbers (1 and "1" are different), objects,
NaN
, etc. It uses===
to distinguish between keys, with one exceptionNaN !== NaN
but you can useNaN
as a key. - Order. The insertion order is remembered. So
[...map]
or[...map.keys()]
has a particular order. - Interface. Object:
obj[key]
orobj.a
(in some language,[]
and[]=
are really part of the interface). Map hasget()
,set()
,has()
,delete()
etc. Note that you can usemap[123]
but that is using it as a plain JS object.
- 钥匙。对象键是字符串或符号。映射键也可以是数字(1 和“1”不同)、对象
NaN
等。它用于===
区分键,只有一个例外,NaN !== NaN
但您可以NaN
用作键。 - 命令。插入顺序被记住。所以
[...map]
或[...map.keys()]
有一个特定的顺序。 - 界面。对象:
obj[key]
或者obj.a
(在某些语言,[]
并且[]=
是真正的接口的一部分)。地图有get()
,set()
,has()
,delete()
等请注意,您可以使用map[123]
,但正在使用它作为一个简单的JS对象。