4

The MDN documentation on Map says:

If you're still not sure which one to use [object or map], ask yourself the following questions:

  • Are keys usually unknown until run time, do you need to look them up dynamically?

  • [...]

Those all are signs that you want a Map for a collection. [...] https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map

But I can set keys for objects dynamicaly with square brackets too (ie. myObject[dynamicKey]).

Is there another reason for using maps when I have dynamic keys other than just for "fitting the Maps purpose" of being a collection?

Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
jpenna
  • 8,426
  • 5
  • 28
  • 36
  • I particularly like the fact that the `set` method returns the map, which is handy in functional programming (like when the accumulator of `reduce` is a map). But this question risks to be opinion based. – trincot Dec 09 '16 at 19:55
  • If that is your *only* criteria, then there's not much difference. It's the rest of the things that you elided from the quote that make the difference, like using things other than strings or symbols for keys, getting the `size`, @trincot mentions `set`, it doesn't have a prototype you need to worry about, etc.. – Heretic Monkey Dec 09 '16 at 19:58
  • 3
    *"But I can set keys for objects dynamicaly with square brackets too (ie. myObject[dynamicKey])."* Yes you can. However, engines are very good at optimizing objects with a fixed set of properties. If you add properties at runtime, the object likely gets deoptimized. Conceptually, use objects as *records* and `Map`s as ... *maps*. – Felix Kling Dec 09 '16 at 20:41

4 Answers4

2

The reason for using Map with arbitrary keys is to avoid name collisions. E.g. with plain object:

let a = {};

function foo(key) {
  console.log(a[key] === undefined ? "Unset" : "Set");
  a[key] = "Hi";
  console.log(a[key]);
  console.log("" + a);
}

foo("goodKey");  // Unset, Hi, [object Object]
foo("toString"); // Set, Hi, TypeError: can't convert a to primitive type

Above we collided with Object.prototype.toString.

With Map we're fine:

let a = new Map();

function foo(key) {
  console.log(a.get(key) === undefined ? "Unset" : "Set");
  a.set(key, "Hi");
  console.log(a.get(key));
  console.log("" + a);
}

foo("goodKey");  // Unset, Hi, [object Map]
foo("toString"); // Unset, Hi, [object Map]
jib
  • 40,579
  • 17
  • 100
  • 158
  • Yes, that's one of the other reasons mentioned in the linked MDN article. But this doesn't answer the OPs question. – Bergi Dec 12 '16 at 04:23
  • @Bergi This *is* the reason you should not use dynamicKey with a plain object. Isn't that what the OP asked? – jib Dec 12 '16 at 04:35
  • Hm, maybe. I understood the question as "Why are maps better than objects for dynamic keys, assuming all else as equal?". – Bergi Dec 12 '16 at 04:39
  • @Bergi Avoiding intermittent `TypeError` seems better. Also, I don't believe the MDN article actually spells out the collision risk. – jib Dec 12 '16 at 04:43
  • MDN only writes "*An Object has a prototype, so there are default keys in the map*". Maybe it should be made more prominent? – Bergi Dec 12 '16 at 04:47
  • 1
    @Bergi I've added a few more words to it. – jib Dec 12 '16 at 04:54
1

It's related to an earlier point in the same document:

An Object has a prototype, so there are default keys in the map.

Traditionally when you iterate through an object (which is what they mean with "look [keys] up dynamically") you'll need to filter out prototype properties:

for( var key in obj ) {
    if( obj.hasOwnProperty(key) ) {
        // do something
    }
}

When you iterate through a Map you can skip the extra check because it doesn't have its own properties mixed together with the prototype properties.

JJJ
  • 32,902
  • 20
  • 89
  • 102
  • You are talking about "looking for" properties, and the thing they are going about using `maps` for dynamic keys is when "setting" it. But that's a good point for "looking up" indeed! – jpenna Dec 09 '16 at 20:54
  • The quote you have in the question talks specifically about looking up keys. If you want to know about setting keys you'll have to quote the relevant section. – JJJ Dec 09 '16 at 21:26
  • Yes, that's one of the other reasons mentioned in the linked MDN article. But this doesn't answer the OPs question. – Bergi Dec 12 '16 at 04:25
  • **If** want to filter out prototype properties (which usually aren't enumerated anyway), [at least do it properly](http://stackoverflow.com/a/13296897/1048572). – Bergi Dec 12 '16 at 04:28
1

Object only support numbers and strings as keys. Maps also support object as key. If your key is unknown until run time you may not be sure what type the key is. So map is safer.

I think you shouldn't use Map when you transpiling your ECMAScript 6 code to ECMAScript 5. The polyfill needed to use Map is not good for performance.

Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
Stefan van de Vooren
  • 2,524
  • 22
  • 19
  • 3
    Actually, objects only have string keys. If you try to use a number (or any other instance) as a key, it will be converted to a string before being added as a key. – Andrew Eisenberg Dec 10 '16 at 03:30
  • 1
    And objects also can have [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) keys. – Michał Perłakowski Dec 10 '16 at 10:12
  • 1
    While your variable values might be unknown at runtime, you hardly ever should have variables of unknown type. – Bergi Dec 12 '16 at 04:24
0

But there is still a problem with assinging values to an object. The reason is that keys are always converted to strings in objects.

For example:

let author1 = { name: "Name1" };
let author2 = { name: "Name2" };

let mostRecentReply = {};

mostRecentReply[author1] = "Test1";
mostRecentReply[author2] = "Test2";

console.log( mostRecentReply[author1] );
console.log( mostRecentReply[author2] );

will output the following:

Test2
Test2

Using a Map solves the problem:

let mostRecentReply = new Map();
mostRecentReply.set(author1, "Test1");
mostRecentReply.set(author2, "Test2");

console.log( mostRecentReply.get(author1) );
console.log( mostRecentReply.get(author2) );

will output:

Test1
Test2
nrhode
  • 913
  • 1
  • 9
  • 27
  • This is because when you are using an object as key it is converted to `[object Object]`, so any object passed as key will refer to the same element. The second assignment using `author2` overrides the first assignment to key `author1`, since both are interpreted as `[object Object]`. But I can't see how your answer fits my question: this is an advantage of using `map` over `object`, but don't really adresses the dynamic key setting – jpenna Dec 10 '16 at 17:25
  • Yes, that's one of the other reasons mentioned in the linked MDN article. But this doesn't answer the OPs question. – Bergi Dec 12 '16 at 04:23