我刚刚发现了这个特点:

Map: Map对象是简单的键/值映射。

这让我很困惑。常规JavaScript对象是字典,那么Map与字典有什么不同呢?从概念上讲,它们是相同的(根据Stack Overflow的另一个问题)

文档也没有帮助:

Map对象是键/值对的集合,其中键和值都可以是任意的ECMAScript语言值。不同的键值只能出现在Map集合中的一个键/值对中。使用创建Map时选择的比较算法进行区分的不同键值。

Map对象可以按插入顺序迭代其元素。Map对象必须使用哈希表或其他机制来实现,这些机制提供的访问时间平均与集合中元素的数量呈次线性关系。本Map对象规范中使用的数据结构仅用于描述Map对象所需的可观察语义。它并不是一个可行的实现模型。

听起来还是像个物件,显然我错过了什么。

为什么JavaScript获得一个(受良好支持的)Map对象?它能做什么?


当前回答

对象的行为类似于字典,因为JavaScript是动态类型的,允许您随时添加或删除属性。

但是Map()更好,因为它:

提供get、set、has和delete方法。 接受任何类型的键,而不仅仅是字符串。 提供一个易于for-of使用的迭代器,并维护结果的顺序。 在迭代或复制过程中不会出现原型和其他属性的边缘情况。 支持数百万项。 非常快。

如果需要字典,则使用Map()。

但是,如果您只使用基于字符串的键,并且需要最大的读取性能,那么对象可能是更好的选择。这是因为JavaScript引擎在后台将对象编译为c++类,并且属性的访问路径比Map().get()的函数调用要快得多。

这些类也被缓存,所以创建一个具有完全相同属性的新对象意味着引擎将重用一个现有的后台类。添加或删除属性会导致类的形状改变,并重新编译支持类,这就是为什么将一个对象用作添加和删除大量内容的字典会非常慢,但是在不更改对象的情况下读取现有键会非常快。

因此,如果您有一个写一次读的繁重的工作负载和字符串键,那么您可以使用对象作为高性能的字典,但对于其他所有事情使用Map()。

其他回答

Map的一个方面在这里没有给予太多关注,那就是查找。根据规格:

Map对象必须使用哈希表或其他方式实现 平均而言,提供次线性访问时间的机制 关于集合中元素的数量。使用的数据结构 在此Map对象规范中仅用于描述 需要Map对象的可观察语义。这并非有意为之 一个可行的实现模型。

对于具有大量项并需要项查找的集合,这将极大地提高性能。

TL;DR -没有指定对象查找,因此它可以按照对象中元素数量的顺序,即O(n)。映射查找必须使用哈希表或类似的方法,因此无论映射大小如何,即O(1),映射查找都是相同的。

根据Mozilla

JavaScript中对象与地图的简短示例。

Object-遵循与map相同的概念,即使用键值对存储数据。但也有细微的差异,使得地图在某些情况下表现更好。

Map-是一种数据结构,有助于以对的形式存储数据。这一对由一个唯一键和映射到该键的值组成。这有助于防止口是心非。

关键的不同点

Map是对象的实例,反之则不然。

var map = new map (); var obj = new Object(); console.log(obj instanceof Map);/ /错误 console.log(映射实例对象);/ /正确的

在Object中,键字段的数据类型被限制为整数、字符串和符号。而在Map中,键字段可以是任何数据类型(整数、数组、对象)

var map = new map();//空 map.set (1, ' 1 '); 地图。(' 1 ', 1); 地图。set('{}', {name:'Hello, World!'}); map.set (12.3, 12.3) map.set([12]、[12345]) (let [key,value] of map.entries()) console.log(键+“-”+值)

在Map中,元素的原始顺序被保留。这在对象的情况下是不正确的。

让obj ={ 1: ' 1 ', “一”:1、 '{}': {name:'Hello world'}, 12.3:12.3, [12]: [100] } console.log (obj)

简介:

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. An object is a nonordered data structure, i.e. the sequence of insertion of key value pairs is not remembered ES6 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 an iterable data structure. This means that the sequence of insertion is remembered and that we can access the elements in e.g. a for..of loop.

关键的不同点:

Map是有序的且可迭代的,而object不是有序的且不可迭代的(也就是说它们没有[符号]。迭代器]属性。但是,你可以在语法中使用for..遍历键。) 我们可以将任何类型的数据作为Map键,而对象只能将数字、字符串或符号作为键。 Map继承自Map.prototype。这提供了各种实用函数和属性,使Map对象的工作更加容易。

例子:

对象:

让obj = {}; //添加属性到对象 obj。Prop1 = 1; Obj [2] = 2; //获取对象属性的nr console.log(种(obj) . length) //删除属性 删除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 () {}

来源:中数

这两个技巧可以帮助你决定是使用Map还是Object:

当键在运行时之前未知时,以及当 所有键都是相同的类型,所有值都是相同的类型。 如果需要将原始值存储为键,则使用映射 因为object将每个键都视为字符串,要么是数字值, 布尔值或任何其他基本值。 当存在操作单个元素的逻辑时使用对象。

来源:键集合

关键的区别是对象只支持字符串和符号键,而映射则支持或多或少的任何键类型。

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 JavaScript 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.

实际上,在Map和对象之间有一些优点和缺点。对象被紧密地集成到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:

({}).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();

还有一些其他的选项、方法、方法等等,它们有不同的起伏(性能、简洁、可移植、可扩展等)。对象作为语言的核心有点奇怪,所以你有很多静态方法来处理它们。

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.

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())
    });
};

如果你纯粹使用map,这还不是很糟糕,但是当你混合类型或使用非标量值作为键时就会出现问题(并不是说JSON是完美的,因为它是IE循环对象引用)。我还没有对它进行测试,但与stringify相比,它可能会严重损害性能。

其他脚本语言通常不会有这样的问题,因为它们为Map、Object和Array提供了显式的非标量类型。Web开发通常是非标量类型的痛苦,你必须处理一些事情,比如PHP使用a /M将数组/Map与对象合并为属性,JavaScript将Map/Object与数组合并为扩展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):

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显然在获取和更新方面有很强的优势,但删除性能非常糟糕。在这种情况下,映射使用了少量的内存(开销),但是只有一个对象/Map要测试数百万个键,映射开销的影响没有很好地表达出来。如果我正确阅读配置文件,内存管理对象似乎也更早释放,这可能是有利于对象的一个好处。

在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有一个很好的领先。在某些情况下,物体比地图的优势是极端的(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使用了5倍多的内存。这个测试使用了四个键,而在上次测试中,我只设置了一个键,所以这可以解释内存开销的减少。我运行了几次这个测试,Map/Object在整体速度方面对Chrome来说或多或少是不分上下的。在用于小对象的Firefox中,总体上比地图有明显的性能优势。

当然,这还不包括个体选择,因为个体选择可能会有很大的差异。我不建议使用这些数据进行微优化。从中可以得到的经验是,对于非常大的键值存储,更强烈地考虑map,而对于小的键值存储,更强烈地考虑对象。

除此之外,这两者的最佳策略是先实现它,然后让它工作。在分析时,重要的是要记住,有时你认为不会慢的东西,当看到它们时,可能会非常慢,因为引擎的怪叫,如对象键删除情况所见。