javascript-300x120

“There’s a new data structure in ES6 called a map. It has this concept wherein you can store data in the map by using a key, then can retrieve the data from the map by passing in the key. It should revolutionize the way that Javascript is … wait a second…”

Isn’t that what an object does?

I’ve been scratching my head about this one for a while. At first glance, a map and an object look an awful lot alike (it turns out that at second and third glance, they still look a lot alike).

There are a few differences between the two, having mostly to do with how prototypes are handled and memory usage. However, understanding the distinction between the two is also a good way to gain some insight into how you can use both maps and objects correctly.

The Territory of Maps

A map is, as mentioned, a way of associated things with things. Notice that this is more than just associating a string label (such as “name” or “gender”) with a value. You can, in fact, think of a map as magical glue. It’s structure is fairly simple:

As an example, here’s what’s becoming a recurring data structure in the series– the game character:

Working examples of the concepts covered here are explorable in the CodePen below.

Already, you should see some differences beginning to pop up between maps and objects. The syntax, for starters, has changed. Objects can be created with the brace operator (var obj = {name:”Jane”, age:24 }) or a two step process with new operator:

Maps, on the other hand, make use of a more Java style notation with an explicit constructor (and yes, appealing to Java developers is one reason for maps):

In other words, maps essentially convert an array of arrays into a js “object-like” object. One (to this author) fundamental flaw was maps is that you cannot pass an object into a map constructor. The following is notallowed:

Being Javascript, a new npm appeared almost immediately after ES6 was first released called, mappify (https://github.com/jlipps/mapify) that rectifies this particular deficit:

Objects use the array operator [] to access keys, (e.g. obj[“myKey”] = myData), and uses the condensed dot notation (obj.myKey = myData) in order to both set and get object values.
Maps, on the other hand, are only accessible through the get() and set() functions (map.set(“myKey”,”myData”), map.get(“myKey”)) to do the same. This arguably provides a single consistent interface for working with maps, though this is a weak argument at best.

Yet another difference is the presence of the has() function, which classical javascript objects don’t have – although that’s a bit deceptive. A given Javascript object does not have, as part of its API, a has() function, but the class Object entity has the similar getOwnProperty() function as part of its prototype: Object.hasOwnProperty(obj,”name”) or obj.hasOwnProperty(“name”).  This isn’t quite identical, since this will retrieve only those property keys that aren’t inherited through the prototype, but at a basic level they are close enough.

Again, this reflects a distinction between a Java and a Javascript mindset. Java developers think inheritance, while Javascript developers think prototype. When Brendan Eich developed the first version of Javascript, he chose a prototype model because he was under a time constraint, and prototype models were generally faster (and more performant) for the kind of use cases than a true interface layer would have been.

The Javascript Object has over the years developed a fair amount of cruft. As an example, if you want to delete a property from a given javascript object, you have to use the delete operator (delete char.species). Maps, arguably, are designed to turn all of the operations on a given object into function calls. Thus, to remove the speciesproperty from a map, you’d call it as map.delete(“species”).

The Evolution of Iteration

Iteration is similarly something that has evolved over time. The earliest objects could not be iterated – you had to know the keys ahead of time. This changed with ES4 (though it didn’t make it to browsers until ES5, ca 2003), in which the in operator was introduced. This made it possible to create a quick test to see if a specific key was in a sequence of keys in an object (if (key in obj){//do something}) (yet another way of performing has()). When used with the for keyword, “in” made it possible to iterate over those keys:

Later (in ES6), the Object.keys(), Object.values(), and (still experimental) Object.entries() functions were added, along with the of keyword. These functions actually prove very useful with arrow notation, as you can create arrow chains with these as iterable arrays:

(Note that the last one does not necessarily work in all browsers, even now)

Maps do support the keys(), value() and entries() functions natively.

Moreover, maps are built with iterators in mind. Iterators have been taking an increasing role in Ecmascript 6, and have both their proponents and their detractors. Perhaps not surprisingly, iterators are also far more commonly used in Java than they are in Javascript, and exist primarily as a way of creating a standardized interface for forward linked lists (where the primary operation is next()). Data retrieval systems in particular are increasingly defined with iterator functionality.
In the above example, the expression gameChar.entries() is not an array but an iterator, and the “of” keyword is then used specifically to, well, iterate over that iterator.

ES6 Maps vs. ES5 Objects Grudge Match

Given all this, what are the real benefits of maps? There are in fact a few. Perhaps one of the biggest is dealing with structures where you have an object that holds a collection in which each entry is itsef an object. As an example, consider the following collection, characters from an RPG game:

This kind of structure tends to occur frequently in programming, with the keys here specifically being record keys coming from a database. You can think of them as collections, and they typically end up showing up in web pages as drop-downs or lists after coming in as JSON – so iterators make a lot of sense here.

While populating the map by creating a temporary array might seem the obvious way of working with it, it’s not that efficient an approach. Better would be something like this:

This has now created a populated collection of objects within a map.

Once in this form, one of the other benefits of maps expresses itself. You can use the forEach() function to rapidly sort through such records and create a new map consisting just of those records for which the character is a female mage:

In this particular case, the filtered map contains a live reference to the  record (a javascript object) in question. This may be the goal, but more than likely what was wanted was an inert object, one which duplicates the existing entry. A single change can do precisely this:

Here the Object.create() function takes an object and creates a duplicate.

The forEach function is very similar to the forEach function of an array, but is specifically optimized for utilizing key indexes. As such maps are actually good cache stores for queries, either for holding temporary stores in single page web applications where global state can be maintained, or on a server with a persistent session variable. They are also preferable when dealing with other iterables that store their key-value pairs as two value arrays.

Objects Still Reign

There are a few advanced cases where maps can do things that objects can’t, one of the primary ones being the ability to utilize a binary object as a key. This can be done to create something akin to private variables and functions within the new Class constructs, However, as private variables are likely to be a part of ES7, it may be better to utilize a specialized Javascript library for this, or even to take a cuefrom Javascript legend Douglas Crockford and use regular objects for this

All in all, ES6 maps do have some utility, working best in those scenarios where you have indexed collections rather than arrays of records. You can use the Object.keys() function to convert such a structure to an array, but the Map() will be considerably more efficient for this.

Additionally, it can be argued (though not necessarily convincingly) that Map structures will be much more familiar to Java used coming to Javascript than the rather esoteric Javascript object would be. In reality, though, with the exception of the collection use case, the two have similar performance profiles.

Other than that, it is unlikely that maps will replace objects any time soon. The ES5 Javascript object is simply too versatile, for all its warts, to be thrown out in favor of map structures.

 

Comments

  • Gary Storey
    Reply

    Overall, a nice article. You did forget to add a couple of links: {{article on classes} and {{Template Literal Article URL}}

  • Ryan Scheel
    Reply

    JS Objects do have a way of testing for property existence (.has() on Maps): Either key in object which traverses the prototype chain or Object.hasOwnProperty(object, key) which doesn’t.

    Also, your game character example is terrible. You should be using objects for that. The keys have meaning and if you’re doing any processing at all, those property names aren’t going to change from character to character. They form a logical structure which is what objects are used for.

    You use a Map for when the keys will all be the same type but distinct and the values should also be of their own same type. For example, say you have a bunch of locations that a game character could be at, but you don’t want to have the location be attached to the player. In that case, you could use a Map from game character to location.

    Also, the iterator thing is nice, but objects will eventually have it. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries for example.

  • nnnnnn321
    Reply

    “Another primary difference between objects and maps is that the former uses the length keyword to determine the total number of properties the object has, while the latter uses the size keyword.”

    Huh? Objects don’t have a length property, arrays do.

    • mavmak

      Ofcourse Objects have length property, representing how many keys the object has

      • nnnnnn321

        No they don’t. {}.length is undefined.

        (Of course, that doesn’t mean that there aren’t a number of ways to get a count of how many keys an object has, but there is definitely no built-in .length property.)

  • Hinrich
    Reply

    Nice explanation, thank you! I think you missed a let in some places, for example in for ([key, value] of gameChar1) ... where it should have been for (let [key, value] of gameChar1) ...

  • loveky
    Reply

    “even if you use a dictionary object to retrieve a “null” property with a guess on a label, the act of doing so forces the object to create a new slot. ”

    can you give an example on this?

  • loveky
    Reply

    “Another primary difference between objects and maps is that the former uses the length keyword to determine the total number of properties the object has, while the latter uses the size keyword.”

    does normal objects have a “length” keyword? can you show some examples?

  • Nuwanda
    Reply

    Chaining get calls makes sense, but how would I go about setting the value of intelligence?

    • Crubier

      gameChar1
      .get(“attributes”)
      .set(“intelligence”,18);

  • josep2
    Reply

    Great write up.