JavaScript - Maps

You're undoubtedly already familiar with JavaScript Objects, but did you know that Maps are another approach to build data sets in JavaScript? Currently, you might be solving your problem using plain old JavaScript objects when a map might be a better option.

JavaScript maps differ from objects in a few key ways. Don't be deceived, even when typeof new Map() returns object. The following are some key variations from objects:

  • Maps unlike objects, which, unless specifically constructed, usually contain a prototype object, does not by default contain any keys.
  • Maps will always be placed in the same order as when they were inserted. These days, objects are also like this, but they don't come with the same warranty.
  • Anything, including a function or even an object, can serve as a map's key. In contrast, it needs to be a string or symbol in JavaScript.
  • On jobs that call for the frequent or quick removal or addition of data, they perform better than objects.
  • Unlike objects, Maps are iterable by default.

Let's look at how maps function, as they have so many advantages.

Creating a Map

The new Map() constructor is used to start any map in JavaScript. Let's make a map called "myFirstMap" as an illustration: 

let myFirstMap = new Map();

The distinction is that you need to utilize particular Map-specific methods in order to set, get, or delete keys from a map. I can use the following method to update the value of someValue with the key firstKey:

let myFirstMap = new Map();

myFirstMap.set('firstKey', 'someValue');

Delete an item from Map

In order to remove a key from a JavaScript map, we would need to use the delete() method:

let myFirstMap = new Map();

myFirstMap.set('firstKey', 'someValue');
myFirstMap.delete('firstKey');

You may also use clear() to completely erase the map and remove all of its contents:

let myFirstMap = new Map();

myFirstMap.set('firstKey', 'someValue');
myFirstMap.clear();
console.log(myFirstMap); // Returns Map(0)

Fetching a key from Map

The use of get() to obtain the value of firstKey is identical to that of the other methods:

let myFirstMap = new Map();

myFirstMap.set('firstKey', 'someValue');
myFirstMap.get('firstKey') // 'someValue'

Checking if a key exists in Map

If we want to see if a map contains a specific key, JavaScript Maps also provide a function called has() that we can use:

let myFirstMap = new Map();

myFirstMap.set('firstKey', 'someValue');
myFirstMap.has('firstKey') // true

Use of standard Object attributes with Maps is not advised

Maps in JavaScript also have their share of peculiarities. Strangely, maps can support object notation as well. For instance, it appears that this works:

let myFirstMap = new Map();

myFirstMap['firstKey'] = 'someValue';
console.log(myFirstMap); // Map(0) { firstKey: 'someValue' }

However, you should not do this! You are only generating an object; you are not adding new entries to the map itself. Therefore, all the advantages of JavaScript maps will be lost.

Checking the size of Map

Counting the number of keys in a map is another practical use where maps seem to be a little more user-friendly than objects. The size() method, which provides the number of keys, can be used for this:

let myFirstMap = new Map();

myFirstMap.set('firstKey', 'someValue');
myFirstMap.size // 1

In order to determine an object's size, we commonly combine Object.keys() and length:

let myObj = { "name" : "John" };
let sizeOfObj = Object.keys(myObj).length; // 1

Using Maps with non-string keys

As I previously indicated, objects only permit strings and symbols, but JavaScript Maps permit non-traditional keys like functions and objects. This is true, for instance, in a map:

let myFirstMap = new Map();
let myFunction = function() { return "someReturn"; }
myFirstMap.set(myFunction, "value");

Map keys are reference-based rather than value-based. Therefore, even though the following actually works:

let myFirstMap = new Map();
let myFunction = function() { return "someReturn"; }
myFirstMap.set(myFunction, "value");

myFirstMap.get(myFunction); // Returns "someReturn"

This will not:

let myFirstMap = new Map();
let myFunction = function() { return "someReturn"; }
myFirstMap.set(myFunction, "value");

myFirstMap.get(function() { return "someReturn"; }); // Returns undefined
myFirstMap.get('someReturn'); // Returns undefined

The reason for this is that even if function() and myFunction have the same value, they are not stored in the same location in the system memory. Consequently, they are not quite equal. MyFirstMap.get('someReturn') also returns undefined, since maps do not function on return values.

The same example also applies to objects, with comparable outcomes:

let myFirstMap = new Map();
let myObject = { "someKey" : "someValue" }
myFirstMap.set(myObject, "value");

myFirstMap.get({ "someKey" : "someValue" }); // Returns undefined
myFirstMap.get(myObject); // Returns 'value'

Merging JavaScript Maps

The spread syntax can be used to combine multiple maps into one, much like it can be used to combine objects. Here, for instance, I use the spread syntax to combine myFirstMap and mySecondMap into myNewMap.

let myFirstMap = new Map();
myFirstMap.set("some", "value");
let mySecondMap = new Map();
mySecondMap.set("someOther", "value");

let myNewMap = new Map([...myFirstMap, ...mySecondMap]);

console.log(myNewMap);
// Map(2) { some: "value", someOther: "value" }

Iterating on a Map

As previously stated, maps are iterable by nature. Usually, we have to use a function like Object.keys if we want to iterate through objects. In the end, this means that we may use forEach on any map as follows:

let myFirstMap = new Map();
myFirstMap.set("some", "value");
myFirstMap.set("someOther", "value");

myFirstMap.forEach(function(value, key, map) {
    // value -> the value of that key in the map
    // key -> the key for this item in the map
    // map -> the entire map
    console.log(value, key, map);
})

Iterating on a JavaScript Map using for

A map can also be iterated on using the for(let... of) command! Each item is returned as an array containing the key and value if you do that. For instance:

let myFirstMap = new Map();
myFirstMap.set("some", "value");

for(let x of myFirstMap) {
    // Returns [ 'some', 'value' ]
    console.log(x);
}

Iterating over values or keys in JavaScript Maps

Using the values() or entries() methods in Javascript is another nice approach to iterate over values or keys. For the values and items in a map, they, respectively, return a new iterator. Therefore, exactly like in generator functions, we may use the next() procedures to get the next key or value.

Let's examine the operation of entries() as an example:

let myFirstMap = new Map();
myFirstMap.set("some", "value");
myFirstMap.set("someOther", "value");
myFirstMap.set("aFinal", "value");

let allKeys = myFirstMap.entries();
console.log(allKeys); // Returns MapIterator {} object

console.log(allKeys.next()); // Returns { value: [ 'some', 'value' ], done: false }
console.log(allKeys.next().value); // Returns [ 'some', 'value' ]

allKeys.next() returned an object to us. This object's value is ["some","value"], which is an array of the first object in our map. To add the ensuing elements to the map, we can keep calling next(). Really cool We may repeat the process using only values this time:

let myFirstMap = new Map();
myFirstMap.set("some", "value");
myFirstMap.set("someOther", "value");
myFirstMap.set("aFinal", "value");

let allValues = myFirstMap.values();
console.log(allValues); // Returns MapIterator {} object

console.log(allValues.next()); // Returns { value: 'value' done: false }
console.log(allValues.next().value); // Returns 'value'

These kinds of iterators are handy in a few certain circumstances and might be a fun way to cycle over all the data in your Map.

Serialization of Maps in Javascript

One of maps' downsides, according to some users, is that JSON.parse() and JSON.stringify cannot be used to efficiently serialise them. A Map's object is empty if we merely supply it with entries, so attempting to do so results in an empty object, which makes some sense.

let myFirstMap = new Map();
myFirstMap.set("some", "value");
myFirstMap.set("someOther", "value");
myFirstMap.set("aFinal", "value");

// Returns {}
console.log(JSON.stringify(myFirstMap));

If you utilise maps, the only practical way to serialise a map is to convert it to an object or an array, which basically means you'll need to keep some separate helper functions around to handle this for you. For instance, after converting our Map to an array using Array.from(), we may serialise it using JSON.stringify().

let myFirstMap = new Map();
myFirstMap.set("some", "value");
myFirstMap.set("someOther", "value");
myFirstMap.set("aFinal", "value");

let arrayMap = Array.from(myFirstMap);

// Returns [["some","value"],["someOther","value"],["aFinal","value"]]
console.log(JSON.stringify(arrayMap));

Then, we must use JSON.parse() together with new Map() in order to convert it back into a map:

let myFirstMap = new Map();
myFirstMap.set("some", "value");
myFirstMap.set("someOther", "value");
myFirstMap.set("aFinal", "value");

// Turn our map into an array
let arrayMap = Array.from(myFirstMap);

// The JSON stringed version of our map:
let stringifiedMap = JSON.stringify(arrayMap);

// Use new Map(JSON.parse...) to turn our stringed map into a map again:
let getMap = new Map(JSON.parse(stringifiedMap));

// Returns Map(3) {'some' => 'value', 'someOther' => 'value', 'aFinal' => 'value'}
console.log(getMap);

Conclusion

When you don't need all the flexibility of objects and things like the order of your data are crucial, JavaScript Maps are a wonderful way to store data. Additionally, they outperform objects in circumstances when there is a need to frequently add and remove items.

I sincerely hope that the majority of you find the approach covered here to be helpful. Thank you for reading, and please feel free to leave any comments or questions in the comments section below.

Post a Comment

0 Comments