ChatGPT解决这个技术问题 Extra ChatGPT

Map vs Object in JavaScript

I just discovered this feature:

Map: Map objects are simple key/value maps.

That confused me. Regular JavaScript objects are dictionaries, so how is a Map different from a dictionary? Conceptually, they're identical (according to another question on Stack Overflow)

The documentation doesn't help either:

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.

…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?


A
Aryan Beezadhur

According to MDN:

A Map object can iterate its elements in insertion order - a for..of loop will return an array of [key, value] for each iteration.

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.

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.


A downside, presumably, is that a Map requires more memory (within the same order of magnitude, however) in order to maintain the insertion order.
Maps have other features besides orderedness that have been mentioned here (using any object as a key, separation of keys and props, etc.), but FWIW in some cases iteration order of plain object properties is defined by ES2015. See stackoverflow.com/a/32149345.
I did not get the meaning, when you say, An Object has a prototype, so there are default keys in the map. However, this can be bypassed using map = Object.create(null). What are default keys? How keys are related to Object.prototype?
My tests in Chrome showed that maps to not use any significant amount more memory for maintaining order. I thing there was 0.1KB more for a million keys and I don't think that was for maintaining order. However, that ~0.1KB seems to be a constant overhead. If you create a million maps with one key instead and compare it's much bigger than object.
@luxon you're creating an object there. ES6 spec requires the new operator to be used with the Map symbol i.e. new Map for it to create a map object. var a = {} is shorthand for (meaning equivalent to) var a = Object.create(Object.prototype)
P
Peter Mortensen

The key difference is that Objects only support string and Symbol 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 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.

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.

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

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

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.

A substantial disadvantage for Maps is that they are not supported with JSON directly. Parsing is possible, but it has several hangups:

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):

// 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 it 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.

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 JavaScript merges Map/Object with Array extending M/O. Merging complex types is the devil's bane of high level scripting languages.

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

In Firefox for this particular benchmark it is a different story:

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.

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.

For testing many small objects with all the above operations (4 keys):

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 five times more memory. This test used four 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.

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.


Lack of serializability has been a real pain for many developers. Look at the upvote of How do I persist a ES6 Map in localstorage (or elsewhere)? and How do you JSON.stringify an ES6 Map?.
Is the number in miliseconds, bytes or total objects?
Took so ms (something took is short for saying something used, so it uses up time in this case). Though this is an old test and I don't have the benchmark code anymore. It's probably very different now. The delete issue for example I believe is fixed.
Although a Map can any value as a key, the semantics of key look up uses object reference equality, rather than using value semantics, which can cause problems.
In Chrome Object.get is 10 times faster than Map.get. But in Firefox Object.get is a little slower than Map.get. As my application is all about getting the value for a key it sounds like Object is the way to go. Hopefully the Mozilla folks have made Object.get faster than Map.get when your speed tests were made over five years ago.
P
Peter Mortensen

An object behaves like a dictionary because JavaScript is dynamically typed, allowing you to add or remove properties at any time.

But Map() is much better because it:

Provides get, set, has, and delete methods.

Accepts any type for the keys instead of just strings.

Provides an iterator for easy for-of usage and maintains the order of results.

Doesn't have edge cases with prototypes and other properties showing up during iteration or copying.

Supports millions of items.

Is very fast.

If you need a dictionary then use a Map().

However, if you're only using string-based keys and need maximum read performance, then objects might be a better choice. This is because JavaScript engines compile objects down to C++ classes in the background, and the access path for properties is much faster than a function call for Map().get().

These classes are also cached, so creating a new object with the same exact properties means the engine will reuse an existing background class. Adding or removing a property causes the shape of the class to change and the backing class to be re-compiled, which is why using an object as a dictionary with lots of additions and deletions is very slow, but reads of existing keys without changing the object are very fast.

So if you have a write-once read-heavy workload with string keys then you can use an object as a high-performance dictionary, but for everything else use a Map().


Object provides get set has delete etc. functionality too, it's just not quite so elegant (but not bad either). In what way is Map easier to use for iterating? Not sure I can agree.
@Andrew I'm taking about the methods, and the functionality is different as well depending on what you're using and the outcome. Iterating is easier because prototype and native properties don't show up in the loop and uses a normal JS iterator that maintains the same order.
@IdoBleicher The reasons are listed in the answer. Maps are functionally easier to use, follow expected behavior, and usually faster. Also they are not an implementation over an object but a separate data structure which is why it's tied to engine support. Is there anything you want more clarity on?
Part of this answer is plagiarised at DEV, in Why to use Maps over Objects in JS ? [sic] (near "only using string-based keys and need maximum read performance").
10 days later: The report did not change anything (I did receive confirmation emails, etc.). This is blatant plagiarism, so we can conclude that DEV, like Quora, does not take plagiarism seriously. It is one thing to not pursue others that plagiarise your content, but it is a whole other when the plagiarism is on your platform (that you 100% control).
P
Peter Mortensen

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.7 million key/value pairs with Map vs. 11.1 million with a regular object. Almost exactly 50% more pairs with a Map. They both take up about 2 GB of memory before they crash, and so I think may be to do with memory limiting by chrome (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):

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.

Maps can be slower:

Due to the .get function call overhead and lack of internal optimisation, Map can be considerably slower than a plain old JavaScript object for some tasks.


In your opinion, do semantics outweigh performance here? Maps sound perfect if you need a dictionary, but it's hard to accept a slower lookup. Isn't a fast lookup the entire point of dictionaries?
I'd definitely go with plain old objects if you're fine with 11 million key/value pairs and don't care about the pre-existing keys like toString, constructor, etc. (i.e. your keys are extremely unlikely to collide with them). They're easier to work with - e.g. increment is obj[i] = (obj[i] || 0) + 1, whereas with Map it's map.set(i, (map.get(i) || 0) + 1) which still isn't too bad, but it just shows how things can get needlessly messy. Maps definitely have their use-cases, but often a plain object will do.
Note that you can get rid of the default toString, constructor, (etc.) object properties by writing obj = Object.create(null) instead of obj = {}.
Regarding existing properties, aren’t they simply a part of the Object’s prototype? That is, they aren’t distinct properties.
W
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. 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.

Key differences:

A Map is ordered and iterable, whereas a objects is not ordered and not iterable (in the sense that they don't have a [Symbol.iterator] property. However, you can iterate over the keys using for..in syntax.)

We 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 from Map.prototype. This offers all sorts of utility functions and properties which makes working with Map objects a lot easier.

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

Source: MDN


it was a good answer until this line "whereas a objects is not ordered and not iterable", because i definitely can easily iterate through the JS object without "tricks": stackoverflow.com/a/14379304/1115187. I remember that x={}; for(let a of x){} fails with TypeError: x is not iterable, but it is we still can pass through all keys/values of an object
There are indeed ways to iterate over the keys of the object (e.g for..in). But objects are not iterable by default. In the sentence 'whereas a objects is not ordered and not iterable' I mean that an object is not iterable in the sense you cannot get the [Symbol.iterator] property from an object. However, you can iterate over the keys using for..in loop.
D
Dan Dascalescu

In addition to the other answers, I've found that Maps are more unwieldy and verbose to operate with than objects.

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.

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.

https://i.stack.imgur.com/zdJmi.png

Objects can be evaluated by any IDE:

https://i.stack.imgur.com/RR4cd.png


Given all this, it seems like map is a premature optimization.
Added to that, Map’s constructor is less comfortable. Object has a simple literal notation. Not only does Map not have a literal, it won’t take an Object in the constructor. To use a Map, you really have to want it.
P
Peter Mortensen

When to use maps instead of plain JavaScript objects

The plain JavaScript Object { key: 'value' } holds structured data. But a plain JavaScript object has its limitations:

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

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.

Three tips to decide whether to use a Map or an Object

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

1. Map accepts any key type and preserves the type of key:

We know that if the object's key is not a string or symbol then JavaScript 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:

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.

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.

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.

2. Map has no restriction over key names:

For plain JavaScript 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:

const actor= {
    name: 'Harrison Ford',
    toString: 'Actor: Harrison Ford'
};

Now let's define a function, isPlainObject(), to determine if the supplied argument is a plain object and this function uses toString() method to check it:

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.

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:

const userCustomFieldsMap= new Map([['color', 'blue'], ['size', 'medium'], ['toString', 'A blue box']]);

3. Map is iterable:

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.

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.

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.

4. We can easily know the size of a Map

We cannot directly determine the number of properties in a plain object. We need a helper function 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.

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 the map.size property.

const examsMap = new Map([['John Rambo', '80%'], ['James Bond', '60%']]);

console.log(examsMap.size);

P
Peter Mortensen

According to Mozilla

Object vs. Map in JavaScript in a short way with examples.

Object- follows the same concept as that of map i.e. using key-value pair for storing data. But there are slight differences which makes map a better performer in certain situations.

Map- is a data structure which helps in storing the data in the form of pairs. The pair consists of a unique key and a value mapped to the key. It helps prevent duplicity.

Key differences

The Map is an instance of an object but the vice-versa is not true.

var map = new Map(); var obj = new Object(); console.log(obj instanceof Map); // false console.log(map instanceof Object); // true

In Object, the data-type of the key-field is restricted to integer, strings, and symbols. Whereas in Map, the key-field can be of any data-type (integer, an array, an object)

var map = new Map();//Empty map.set(1,'1'); map.set('one', 1); map.set('{}', {name:'Hello, World!'}); map.set(12.3, 12.3) map.set([12],[12345]) for(let [key,value] of map.entries()) console.log(key+'---'+value)

In the Map, the original order of elements is preserved. This is not true in case of objects.

let obj ={ 1:'1', 'one':1, '{}': {name:'Hello world'}, 12.3:12.3, [12]:[100] } console.log(obj)


Note that as of ES2015 object order is in fact predictable, though not necessarily intuitive. More intuitive ordering expectations of Map may make it a more preferable alternative.
P
Peter Mortensen

This is a short way for me to remember it: 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 exception NaN !== NaN but you can use NaN as a key. Order. The insertion order is remembered. So [...map] or [...map.keys()] has a particular order. Interface. Object: obj[key] or obj.a (in some language, [] and []= are really part of the interface). Map has get(), set(), has(), delete() etc. Note that you can use map[123], but that is using it as a plain JavaScript object.


O
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:

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 object obj, so you expect obj.foo to return undefined. But foo could be built-in property inherited from Object.prototype. Or you attempt to create obj.foo by using an assignment, but some setter in Object.prototype runs instead of storing your value. Maps prevent these kind of things. Well, unless some script messes up with Map.prototype. And Object.create(null) would work too, but then you lose the simple object initializer syntax.


P
Peter Mortensen

I came across this post by Minko Gechev which clearly explains the major differences.

https://i.stack.imgur.com/P5hSl.jpg


P
Peter Mortensen

One aspect of the Map that is not given much press here is lookup. According to the specification:

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

For collections that have a huge number of items and require item lookups, this is a huge performance boost.

TL;DR - Object lookup is not specified, so it can be on the order of the number of elements in the object, i.e., O(n). Map lookup must use a hash table or similar, so Map lookup is the same regardless of Map size, i.e. O(1).


Note that that spec leaves the door open to using something like a binary search tree with O(log(n)) lookups (like C++'s std::map) — sublinear doesn't necessarily mean O(1). Also in real-world engines, they are both likely to be implemented as hashmaps (for V8 see v8.dev/blog/fast-properties and medium.com/@bpmxmqd/… for explanations on how the engine decides to use "slow" (dictionary) properties and what that means for storage (HashTable))
Yes, @Dave you are correct. If some other implementation other than a hash-map is used, it will likely not be constant time. But, my original point is still a valid, which seems to be glossed over in the original question. Simply put, if you have a huge number of items and need to regularly look them up, a Map should be your go-to data structure.
P
Peter Mortensen

These two tips can help you to decide whether to use a Map or 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 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.

Source: Keyed collections


These tips do not look particularly helpful especially as it tends not to be easy to partition things off by those criteria. I don't get with the first one why maps are a benefit when keys/values are the same type. It sounds more like it's trying to say use objects like classes/structs, maps like collections. The second one is written poorly not getting to the point. It really means use maps when you have mixed string equivalent types ("1" and 1) or when you need/want to preserve key types. The last I think it the same as first, it's assuming you don't know what an object is so it is vague.
G
Gabriel Petersson

Adding to the usability differences brought up above, if you are more interested in the performance difference on huge objects, plain object seem to be ~2x faster on chrome on setting, updating, and deleting a large amount of data.

Experiment here: https://perf.link/#eyJpZCI6ImNncmtpcGdlMzhsIiwidGl0bGUiOiJGaW5kaW5nIG51bWJlcnMgaW4gYW4gYXJyYXkgb2YgMTAwMCIsImJlZm9yZSI6ImNvbnN0IGRhdGEgPSBbLi4uQXJyYXkoMTAwMDAwKS5rZXlzKCldXG4iLCJ0ZXN0cyI6W3sibmFtZSI6IkZpbmQgaXRlbSAxMDAiLCJjb2RlIjoiY29uc3Qgb2JqID0ge31cbmZvciAoY29uc3QgdiBvZiBkYXRhKSB7XG4gICBvYmpbdl0gPSB2XG59XG5cbmZvciAoY29uc3QgdiBvZiBkYXRhKSB7XG4gICBvYmpbdl0gPSB2ICsgMVxufVxuXG5mb3IgKGNvbnN0IHYgb2YgZGF0YSkge1xuICAgZGVsZXRlIG9ialt2XVxufSIsInJ1bnMiOlsxMTUsMTE1LDExNSwxMTUsMTE1LDExNSwxMDksMTE1LDEwOSwxMTUsMTE1LDExNSwxMDksMTE1LDExNSwxMTUsMTA5LDEwOSwxMDksMTE1LDEwOSwxMDksMTE1LDEwOSwxMTUsMTA5LDEwOSwxMTUsMTA5LDExNSwxMDksMTE1LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDExNSwxMDksMTA5LDEwOSwxMDksMTE1LDEwOSwxMDksMTA5LDEwOSwxMDksMTE1LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMTUsMTA5LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMTUsMTE1LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMDksMTA5LDEwOSwxMDldLCJvcHMiOjExMH0seyJuYW1lIjoiRmluZCBpdGVtIDIwMCIsImNvZGUiOiJjb25zdCBvYmogPSBuZXcgTWFwKClcbmZvciAoY29uc3QgdiBvZiBkYXRhKSB7XG4gICBvYmouc2V0KHYsIHYpXG59XG5cbmZvciAoY29uc3QgdiBvZiBkYXRhKSB7XG4gICBvYmouc2V0KHYsIHYgKyAxKVxufVxuXG5mb3IgKGNvbnN0IHYgb2YgZGF0YSkge1xuICAgb2JqLmRlbGV0ZSh2KVxufSIsInJ1bnMiOls2Myw1Nyw1Nyw2Myw1Nyw2Myw1Nyw1Nyw1Nyw2Myw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw2Myw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1Nyw1N10sIm9wcyI6NTd9XSwidXBkYXRlZCI6IjIwMjItMDYtMTVUMTg6MTQ6MjYuNjA2WiJ9