主要区别在于,对象仅支持字符串和符号键,而地图或多或少支持任何键类型。
如果我这样做,然后我会得到而不是.地图将保留密钥的类型并返回,这很好。地图还允许您使用对象作为键。传统上,要做到这一点,你必须给对象某种唯一的标识符来对它们进行哈希处理(我不认为我见过像JavaScript这样的东西作为标准的一部分)。地图还可以保证保存秩序,因此可以更好地保存,有时可以节省您需要进行一些排序的时间。obj[123] = true
Object.keys(obj)
["123"]
[123]
[123]
getObjectId
在实践中,地图和对象之间有几个优点和缺点。对象在非常紧密地集成到JavaScript的核心中,从而获得了优点和缺点,这使得它们与Map在关键支持方面的差异之外显着区分开来。
一个直接的优点是,您可以对对象提供语法支持,从而轻松访问元素。您还可以通过 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
对象上的污染不仅会使代码更烦人,更慢等,而且还可能对安全性产生潜在后果。
对象不是纯粹的哈希表,但它们正在尝试做更多的事情。你有头痛,比如,不能轻易获得长度()等等。对象并不意味着纯粹用作哈希映射,而是作为动态可扩展对象,因此当您将它们用作纯哈希表时,会出现问题。hasOwnProperty
Object.keys(obj).length
各种常见操作的比较/列表:
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();
还有其他一些选项,方法,方法等,具有不同的起伏(性能,简洁,可移植,可扩展等)。对象作为语言的核心有点奇怪,所以你有很多静态方法来使用它们。
除了Maps保留键类型以及能够支持对象作为键之类的东西的优势之外,它们还与对象具有的副作用隔离开来。Map是一个纯粹的哈希值,尝试同时成为一个对象不会感到困惑。地图还可以使用代理功能轻松扩展。对象当前有一个代理类,但是性能和内存使用量很严峻,实际上创建自己的代理,看起来像对象的Map,目前比代理更好。
地图的一个重大缺点是 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;
});
以上将引入严重的性能影响,并且也不会支持任何字符串键。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())
});
};
如果您纯粹使用Maps,这并不是那么糟糕,但是当您混合类型或使用非标量值作为键时,它会遇到问题(并不是说JSON非常适合这种问题,因为它是IE循环对象引用)。我还没有测试过它,但与Stringify相比,它可能会严重损害性能。
其他脚本语言通常没有这样的问题,因为它们具有Map,Object和Array的显式非标量类型。对于非标量类型,Web开发通常是一种痛苦,您必须处理诸如PHP将Array/Map与Object合并之类的事情,使用A / M的属性和JavaScript合并Map/Object与数组扩展M / O。
到目前为止,这些主要是围绕实现的问题,但基本操作的性能也很重要。性能也很复杂,因为它取决于发动机和使用情况。带着一粒盐来测试,因为我不能排除任何错误(我必须匆忙)。您还应该运行自己的测试来确认,因为我只检查非常具体的简单场景,以仅给出粗略的指示。根据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显然在获取和更新方面具有很强的优势,但删除性能却很糟糕。在这种情况下,地图使用的内存量(开销)略多,但是由于仅使用数百万个键测试了一个对象/映射,因此对映射的开销的影响没有得到很好的表达。使用内存管理对象,如果我正确阅读配置文件,则对象似乎也更早释放,这可能是有利于对象的一个好处。
在 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
我应该立即指出,在这个特定的基准测试中,从Firefox中的对象中删除不会引起任何问题,但是在其他基准测试中,它引起了问题,特别是当有很多键时,就像在Chrome中一样。在 Firefox 中,对于大型集合,地图显然更胜一筹。
然而,这并不是故事的结局,那么许多小物体或地图呢?我已经对此进行了快速基准测试,但不是详尽的基准测试(设置/获取),在上述操作中使用少量键时性能最佳。此测试更多地涉及内存和初始化。
Map Create: 69 // new Map
Object Create: 34 // {}
同样,这些数字各不相同,但基本上Object具有良好的领先优势。在某些情况下,Objects比地图领先(约10倍),但平均而言,它比地图高出约2-3倍。似乎极端的性能峰值可以双向工作。我只在Chrome和创建中对此进行了测试,以分析内存使用情况和开销。我很惊讶地看到,在Chrome中,使用一个键的地图似乎比使用一个键的对象使用大约30倍的内存。
要使用上述所有操作(4 个键)测试许多小对象:
Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139
在内存分配方面,它们在释放/GC方面表现相同,但Map使用的内存是其五倍。这个测试使用了四个键,与上一个测试一样,我只设置了一个键,这样可以解释内存开销的减少。我运行了这个测试几次,就整体速度而言,Map/Object或多或少地与Chrome并驾齐驱。在 Firefox 中,对于小对象,总体上比地图有明显的性能优势。
当然,这不包括可能变化很大的个别选项。我不会建议用这些数字进行微观优化。从中可以得到的是,根据经验,对于非常大的键值存储,更强烈地考虑地图,对于小键值存储,请考虑对象。
除此之外,最好的策略是用这两个来实现它,并首先让它工作。在分析时,重要的是要记住,有时您在查看它们时不会认为会很慢的事情可能会非常慢,因为引擎怪癖,如对象键删除情况所示。