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

var map = new Map(); map.set(something, somethingElse); var data = map.get(something); var somethingExists = map.has(something);

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

var gameChar1 = new Map(); gameChar1.set("name","Aleria"); console.log(gameChar1.get("name")) // "Aleria" console.log(gameChar1.has("name")) // true

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:

var obj = new Object; Obj.name = "Aleria"; Obj.species = "half-elf";

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

var map = new Map([["name","Aleria"],["species","half-elf"]]);

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:

var map = new Map({name:"Aleria",species:"half-elf"});

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:

import{ mapify } from 'es6-mapify'; var map = mapify({name:"Aleria",species:"half-elf"}); // map {name:"Aleria",species:"half-elf"} import{ demapify } from 'es6-mapify'; var map = mapify({name:"Aleria",species:"half-elf"}); // map {name:"Aleria",species:"half-elf"} var demap = demapify(map); // {name:"Aleria",species: "half-elf"}

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:

for (key in obj){console.log(key,obj[key])}.

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:

Object.keys(obj).map((key)=>console.log(key)).join(", ") // "name","species" Object.values(obj).map((obj)=>console.log(obj)).join(", ") // "Aleria","half-elf" Object.entries(obj).map((key,obj)=>console.log(key,value)) // "name","Aleria" // "species","half-elf"

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

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

let gameChar = new Map([ ["name","Aleria"], ["gender","female"], ["species","half-elf"], ["vocation","mage"] ]); for ( [key, value] of gameChar.entries()){console.log(`${key} : ${value}`)}; // "name:Aleria" // "gender:female" // "species:half-elf" // "vocation:mage"

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:

var charSet = { aler102:{id:"aler102", name:"Aleria", gender:"female",vocation:"mage",species:"half-elf"}, thor319:{id:"thor312", name:"Thor", gender:"male",vocation:"warrior",species:"half-orc"}, rean831:{id:"rean831", name:"Reanna", gender:"female",vocation:"monk",species:"human"}, gunt615:{id:"gunt615", name:"Gunther", gender:"male",vocation:"smith",species:"human"}, ness789:{id:"ness789", name:"Nessa", gender:"female",vocation:"mage",species:"human"} }

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:

Var charMap = new Map(); Object.keys(charSet).forEach((key)=>map.set(key,charSet[key]);

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:

var targetMap = new Map(); charMap.forEach((entry,key)=> {if(entry.gender=="female" && entry.vocation=="mage") {targetMap.set(key,entry)} }); console.log(targetMap); // Map {"aler102" => Object {id: "aler102", name: "Aleria", gender: "female", vocation: "mage", species: "half-elf"}, "ness789" => Object {id: "ness789", name: "Nessa", gender: "female", vocation: "mage", species: "human"}}

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:

charMap.forEach((entry,key)=> {if(entry.gender=="female" && entry.vocation=="mage") {targetMap.set(key,Object.create(entry))} });

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.