Skip to main content

ES6 keyed collections: Maps and sets

JavaScript keyed collections are structured collections of data that store values and provide methods for easily accessing them. Sounds a lot like arrays and objects, right?

Keyed collections were actually introduced with ES6 as an alternative to arrays and objects, which were the only data structures available in JS to that point. Although they were good enough for storing data, objects and arrays had some limitations and were a bit painful to work with.

For example, to iterate over an object or to sort it, you had to first convert it to an array, then perform those operations. With arrays, looping was not a problem, but if you needed to pull out a specific value without its index, it was not at all straightforward.

Keyed collections — namely, MapSetWeakMap, and WeakSet— were introduced to solve these issues and to make working with values or key-value pairs easier.

In this guide, we’ll take a closer look at maps and sets and examine their syntax, how they differ from objects and arrays, and what methods they offer. We’ll also compare their performance.

JavaScript sets

JavaScript sets resemble arrays in the sense that they are also collections of values. But, unlike arrays, these data collections can only include unique values. In other words, you can’t have duplicates in a set.

The second difference between arrays and sets is that inside sets, the values are stored in no particular order, so you can just call them by their name.

Let’s create a new set to see it in action. You can find the examples used in this first exercise on JSFiddle.

let events = new Set();
let event1 = { type: "concert", day: "Saturday" };
let event2 = { type: "book launch", day: "Wednesday"};
let event3 = { type: "conference", day: "Thursday"};
let event4 = { type: "meet up", day: "Monday" };
// Let's add each event to the set
events.add(event1);
events.add(event2);
events.add(event3);
events.add(event4);

As you can see, the syntax is very simple. You create a new set with the new Set() method and use the add() method to push the values in the set.

To see what values the set contains, you can use the values() method inside a for  of loop.

for (let item of events.values()) {
  console.log(item);
}

If you want to check whether a specific value is found inside the set, you can use the has(value) method. To delete an item, you can use the delete(value) method.

console.log(events.has(event2));

events.delete(event3);
for (let value of events) {
        console.log(value)
}

Other useful methods available for sets include clear() and size(). The former removes all the items from the set, while the latter returns the number of elements in the set.

We’ve established that this type of collection can only contain unique values. What happens if we want to add an event twice?

let cities = new Set();

let city1 = { name: "London" };
let city2 = { name: "Barcelona"};
let city3 = { name: "Milan"};

cities.add(city1);
cities.add(city2);
cities.add(city1);
cities.add(city3);

cities.forEach((city, cities) => {
  console.log(city);
});

This will list the names of the three cities, each of them only once.

As you can see, the syntax and the methods of sets are very simple and easy to use. But when and why would you use this type of keyed collection?

Set vs. array: Uses and performance

Converting sets to arrays and vice versa is easy to do and very handy if you want to perform operations such as filtering and returning the unique values from a data collection.

Here’s how to turn a set into an array:

let set = new Set([9, 15, "a string", {"objectKey": "objectValue"}]);
set.add(true);

let arr = [...set]; // destructuring

console.log(arr); fj

// Outputs [9, 15, "a string", {objectKey: "objectValue"}, true]

As you can see, the set contains a combination of data types this time: numbers, a string, an object, and a boolean. To convert this set to an array, we used restructuring.

Here’s how to convert an array to a set:

let arr2 = [9, 15, "a string", {"objectKey": "objectValue"}];

let arr2converted = [...new Set(arr2)];

console.log(arr2converted);

// Outputs [9, 15, "a string", {objectKey: "objectValue"}, true]

Again, you can find the code for this exercise on JDFiddle.

Now let’s look at an example where we have duplicate items in an array and we want to filter them out. We can do this in two ways:

// Method 1

let users = ["John", "Murray", "Jane", "Jane", "Anne"];

function unique(users) {
        return Array.from(new Set(users));
}

console.log(unique(users));

// Method 2

let set = new Set(users);
let arrFromSet = [...set];

console.log(arrFromSet);

The code for this exercise is available on JSFiddle.

Finally, let’s say we want to add all the users from above to a new set and a new array. Let’s see which collection performs the operation faster.

let arr = [], set = new Set();
let users = ["John", "Murray", "Jane", "Jane", "Anne", "John", "Murray", "Jane", "Jane", "Anne"];

for (let i = 0; i < users.length; i++) {
  arr.push(users[i]);
  set.add(users[i]);
}

let result;

console.time('Array'); 
result = arr.indexOf("Anne") !== -1; 
console.timeEnd('Array');

console.time('Set'); 
result = set.has("Anne"); 
console.timeEnd('Set');

Run this code directly in your console. Here are the results:

Array: 0.013916015625ms
Set: 0.0078125ms

The difference is very small here, but the set is faster. If you perform such operations on big data sets, the latter collection type is a better choice.

JavaScript maps

Maps can be used instead of objects in situations where you need to use a key-value pair but want a bit more flexibility. As the name implies, they’re just used to map a key to a value.

In JavaScript objects, each key in the pair needs to be either a string or a symbol. In maps, however, the keys are unrestricted, meaning you can use another object, a function, or even a primitive type as the key.

Here’s what the map syntax looks like:

let users = [{
    id: 1,
    name: 'John'
  },
  {
    id: 2,
    name: 'Murray'
  },
  {
    id: 3,
    name: 'Jane'
  },
  {
    id: 4,
    name: 'Jane'
  },
  {
    id: 5,
    name: 'Anne'
  }
]

let userNames = users.map(function(user) {
  console.log(user.name)
});

Without this type of keyed collection, you would have to first create an empty array into which you would then push all user names.

let userNms = [];

users.forEach(function (user) {
  userNms.push(user.name);
});

console.log(userNms);

Here’s the code for this exercise.

Maps use methods similar to those used by sets: cleardeletehasvaluesentriesforEach. We won’t cover them all, but we’ll look at three methods that are specific to maps: set()get(), and entries().

Set() adds a key-value pair to the Map object, while get()retrieves the value for the specified key.

Here’s an example:

const user1 = new Map();
user1.set('id', 1);
user1.set('name', 'John');

console.log(user1.get('id'));

What if we want to get the key-value pairs from the map collection? We can use the entries() method with an iterator.

const user1 = new Map();
user1.set('id', 1);
user1.set('name', 'John');

console.log(user1.get('id'));

let iterator = user1.entries();

console.log(iterator.next().value);
console.log(iterator.next().value);

Here’s the code.

Map vs. object: Uses and performance

Maps and objects are very much alike, but the main difference is that inside a map, any data type can be a key, so you’re not limited to strings. This is extremely useful when you want to store object-related data but don’t want to add it to the object itself or use an array of objects due to their limitations.

You can also directly iterate over the keys or values of a map. With objects, you would first have to convert them to an array, which isn’t always practical. Below is an example of iteration over a map collection.

let userIDs = new Map();

let user1 = {name: 'John'}, user2 = {name: 'Murray'}, user3 = {name: 'Jane'};

userIDs.set(user1, 1) .set(user2, 2) .set(user3, 3);

// Method 1

for (let [name, id] of userIDs) {
  console.log(name);
  console.log(id);
}

// Method 2

userIDs.forEach((name, id) => {
  console.log(name);
  console.log(id);
});

Here’s the code for this example.

To convert an object to a map, we can use the Object.entries()method.

const obj = {
  'name': John,
  'id': 1,
}

const map = new Map(Object.entries(obj));

console.log(map.get('name')) 
// Outputs John

Now, let’s compare an object to a map and see how they do in terms of performance. We’ll use the same example as before, when we compared sets with arrays.

let obj = {}, map = new Map();

let users = ["John", "Murray", "Jane", "Jane", "Anne", "John", "Murray", "Jane", "Jane", "Anne"];

for (let i = 0; i < users.length; i++) {
  obj[i] = i;
  map.set(i, i);
}

let result;

console.time('Object'); 
result = obj.hasOwnProperty("Anne"); 
console.timeEnd('Object');

console.time('Map'); 
result = map.has("Anne"); 
console.timeEnd('Map');

You can find the code for this exercise here.

The performance of these two collections is as follows.

Object: 0.031982421875ms
Map: 0.0146484375ms

For comparison, the performance for array and set was:

Array: 0.013916015625ms
Set: 0.0078125ms

As you can see, although maps and sets are similar to arrays and objects, these newer keyed collections are more flexible, easier to iterate over, and higher-performing.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...