# Learn Sets and Maps in JavaScript for Beginners

ES6 includes two data structures which help programmers get work done without reinventing the wheel. These two data structures are those of Sets and Maps. A Set can be thought of as a collection of elements that are both unordered, and unique. That is the key to remember. The purpose of a Set is to guarantee the uniqueness of its items. A particular element is a member of the set if the set contains the element. The set does not change if you add an element that is already a member of the set. A Map, on the other hand, is a collection of key-value pairs. They are quite similar to objects, however, maps can have keys of any type, and the keys are not converted to strings. Let’s expand our learning of Sets and Maps in ES6 right now.

In this article a look into two new constructs that were introduced in the JavaScript ES6 specification:

1. `Set`- The Set object allows you to store unique values of any type.
2. `Map`- The Map object allows you to store key-value pairs and remembers the original insertion order of the keys.

The objective of these new constructs is to provide easier and more efficient ways to structure and access data under certain use cases. In this article, we will look at how Sets and Maps work and explore some of the operations that can be performed on them.

## Sets

The MDN docs describe the Set object as:

A collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Sets collection.

The JavaScript Set object behaves similarly to the mathematical Set. It allows the addition of distinct values and provides useful methods on its prototype. These methods include - addition, removal and looping over of the items present in the Set.

### Array vs Set

An array, like a set, is a data structure that allows addition, removal and looping operations on its items. However, an array differs from a set in the sense that it permits the addition of duplicate values and its operations are relatively slower.

Searching through an array has a linear time complexity of O(n), the same as inserting an element in the middle of an array. This means that the running time for searching and inserting items in an array grows as the size of the array increases.

JavaScript’s Push and Pop array methods have a run-time of O(1) which means that: these operations will have a constant time of execution regardless of the size of the array size. However, in practice, the Push operation is O(n) as copy costs are incurred when new contiguous memory locations are allocated to the newly formed array.

Essential Reading: Learn React from Scratch! (2020 Edition)

In contrast, all insert, delete and search operations for Sets have a running time of just O(1).

### Creating a Set

Let’s create a Set:

``````const set = new Set();

console.log(set); // Set {}
``````

### Initializing a Set

To initialize a set, we can pass an array of values to the Set constructor, this will create a Set with those values:

``````const confectioneries = new Set(['oreo', 'marshmallow','oreo', 'kitkat', 'gingerbread']);

console.log(confectioneries); // result: Set { 'oreo', 'marshmallow', 'kitkat', 'gingerbread' }
``````

In the snippet above, the duplicate value “oreo” is quietly removed from the Set and only unique values are returned.

We can add more items to a Set using the `add()` method. This method adds a new value to the Set object and returns the Set. An attempt to add a duplicate item to the Set object wouldn’t return an error, instead, the item will not be added.

Let’s go over an example:

``````const confectioneries = new Set(['oreo', 'marshmallow', 'kitkat', 'oreo','gingerbread']);

console.log(confectioneries); //_ log result: Set { 'oreo', 'marshmallow', 'kitkat', 'gingerbread', 'donut' } _

console.log(confectioneries); //_ log result: Set { 'oreo', 'marshmallow', 'kitkat', 'gingerbread', 'donut' } _
``````

### Deleting Items

With sets, we can delete items using either of these commands:

• `delete()`
• `clear()`

To use the `delete()` method, the value to be deleted is passed to the method. The method will return a Boolean value `true` if the deletion was successful and `false` if otherwise. We can delete all the elements of the Set object using the `clear()` method.

Let’s try out both methods in this example:

``````confectioneries.delete('kitkat');

console.log(confectioneries); //_ log result: Set { 'oreo', 'marshmallow', 'gingerbread', 'donut' }_

confectioneries.clear();

console.log(confectioneries); // log result: Set {}
``````

### Size of a Set

We can get the size of a Set using the `size` property on the Set prototype. This is similar to the `length` property for Arrays:

``````const confectioneries = new Set(['oreo', 'marshmallow', 'kitkat', 'oreo','gingerbread']);

console.log(confectioneries.size); // log result: 5
``````

### Searching for Items

We may need to know if a Set has a particular item. This can be accomplished using the `has()` method. The `has()` method returns `true` if the item is in the Set object, and `false` if it isn’t:

``````console.log(confectioneries.has('marshmallow')); // log result: true
``````

### Returning the Items in a Set

We can return the items in a Set object in the same insertion order using the `values()`method. This method returns a new `setIterator` object . A similar method for returning the items of a set is the `keys()` method:

``````console.log(confectioneries.values()); // _log result: _[_Set Iterator] { 'oreo', 'marshmallow', 'kitkat', 'gingerbread', 'donut' }_

console.log(confectioneries.keys()); //_ log result: _[_Set Iterator] { 'oreo', 'marshmallow', 'kitkat', 'gingerbread', 'donut' }_
``````

The `setIterator` object is an Iterator object because it implements the Iteratable and Iterator protocols. The `Iterable` protocol specifies a way to iterate through a set of values using loop constructs. It also makes it possible for the values to be iterated using the `next()` method. When we call `next()` on a `setIterator` object, we get the next value in the iteration and a `false` until all values of the Set have been iterated over:

``````let iterator = confectioneries.values();

console.log( iterator.next()); // _{ value: 'oreo', done: false }
_
console.log( iterator.next()); // _{ value: 'marshmallow', done: false }
_
console.log( iterator.next()); //_ { value: 'kitkat', done: false }
_
console.log( iterator.next()); //_ { value: 'gingerbread', done: false }
_
console.log( iterator.next()); //_ { value: 'donut', done: false }
_
console.log( iterator.next()); // _{ value: undefined, done: true }_
``````

Since Sets implement the Iterable protocol, loop constructs such as `for ...of`can be used as shown below:

``````for (let confectionery of confectioneries) {
console.log(confectionery);
}

/_ _console.log() result
oreo
marshmallow
kitkat
donut
__/
``````

### WeakSets

WeakSets provide extra flexibility when working with the Set data structure. They are different from regular Sets in that they only accept objects and are not iterable; they can’t be looped over, and do not have a `clear()` method. How then do they provide extra flexibility? We’ll see in a bit.

We can create a `WeakSet`using the `WeakSet` constructor:

``````let user1 = {name: 'user 1', email: 'user1@example.com'};
let user2 = {name: 'user 2', email: 'user2@example.com'};
let user3 = {name: 'user 3', email: 'user3@example.com'};

const users = new WeakSet([user1, user2, user3]);
``````

The code above creates a new `WeakSet` object, adding items other than objects returns a `TypeError:`

``````users.add('user 4');

console.log(users); // TypeError: Invalid value used in weak set
``````

Since WeakSets do not have a `clear()` method, objects can only be deleted by setting them to `null`. This works because the JavaScript Engine’s garbage collection algorithms will automatically free up memory allocated to the null object, hence deleting it from the WeakSet.

This is wonderful because the WeakSets objects set to null are garbage-collected while the program is still running, hence, reducing memory consumption and preventing memory leakage, especially when dealing with huge amounts of data that are generated asynchronously.

Within this feature lies a chance for you to write light-weight solutions to programming problems without having to bother with the details of memory management.

## Maps

JavaScript Maps are objects designed to efficiently store and retrieve items based on a unique key for each item. A Map stores key-value pairs where both keys and values could be either primitive values or objects, or both.

The MDN docs describe the Map object as:

The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value. A Map object iterates its elements in insertion order — a for…of loop returns an array of [key, value] for each iteration.

### Creating a Map

Akin to Sets, Maps are easy to create. Let’s create a Map using the Map constructor:

``````const users = new Map();

console.log(users); // Map {}
``````

Key-value pairs are added to a Map using the `set()` method. This method takes in two arguments, the first being the key and the second, the value, which is referenced by the key:

``````users.set('John Doe', {
email: 'johndoe@example.com',
});

users.set('Jane Doe', {
email: 'janedoe@example.com',
});

console.log(users);

/__ console.log result
Map {
'John Doe' => { email: 'johndoe@example.com'},
'Jane Doe' => { email: 'janedoe@example.com'} }
__/
``````

Unlike Sets which discard duplicate keys, Maps will update the value attached to that key:

``````users.set('John Doe', {
email: 'johndoe477@example.com',
});

console.log(users);

/__ console.log result
Map {
'John Doe' => {email: 'johndoe477@example.com'},
'Jane Doe' => { email: 'janedoe@example.com'} }
__/
``````

When you run the example above, `John Doe's` email will neatly be replaced. Smooth.

### Deleting Items

As with Sets, key-value pairs can be deleted using the `delete()` method. The key to be deleted is passed to the `delete()` method as shown below:

``````users.delete('Jane Doe');
``````

Maps also have a `clear()` method, this removes all key-value pairs from the Map object:

``````users.clear();

console.log(users); // Map {}
``````

### Searching for Items

Maps also have a `has()` method which checks if a key exists in a Map. This method will return `true` if the key is in the Map and `false` if it is not:

``````let users = new Map();

users.set('John Doe', {
email: 'johndoe@example.com',
});

users.set('Jane Doe', {
email: 'janedoe@example.com',
});

console.log(users.has('John Doe')); // true
``````

### Returning the Value of a Map item

The value of a key in a Map object can be gotten using the `get` method on the Map prototype:

``````console.log(users.get('Jane Doe'); // { email: 'janedoe@example.com' }
``````

It is possible to get all the keys and values of a Map object using the `keys()` and `values()` methods respectively. These methods both return a new `MapIterator` object which has a `next()` method that can be used to loop through the items of the Map:

``````let userKeys = users.keys();

console.log(userKeys.next()); // { value: 'John Doe', done: false }

let userValues = users.values();

console.log(userValues.next()); // _{ value: { email: 'johndoe@example.com' }, done: false }_
``````

As with Sets, loop constructs such as `for...of` and `forEach()` can be used to loop through Map items:

``````for (let user of users) {
console.log('[for...of]: ', user);
}

/_ Log result
_[_for...of]:  _[_ 'John Doe', { email: 'johndoe@example.com' } ]
_[_for...of]:  _[___ 'Jane Doe', { email: 'janedoe@example.com' } ]
_/

users.forEach((value, key) => console.log('[__forEach()]:  ', key, value));

/*_ Log result
[__forEach()]:   John Doe { email: 'johndoe@example.com' }
_[_forEach()]:   Jane Doe { email: 'janedoe@example.com' }
*_/
``````

### WeakMaps

As with WeakSets, WeakMaps differ from regular Map objects. WeakMaps only accept objects as keys, are not iterable and do not have a `clear()` method.

A WeakMap constructor is used to create a WeakMap.

Let’s look at an example:

``````let users = new WeakMap();

const user1 = {
name: 'John Doe',
};
const user2 = {
name: 'Jane Doe',
};

users.set(user1, {
email: 'johndoe@example.com',
});

users.set(user2, {
email: 'janedoe@example.com',
});
``````

As with WeakSets, setting the key of a WeakMap object to null will implicitly garbage collect that item:

``````user1 = null;
``````

This has the same advantages as with WeakSets in providing easier memory management.

## Conclusion

In this article, we’ve taken a look at Sets and Maps and how they handle unique items and key-value pairs respectively. These useful data structures provide easier and more efficient ways to structure and access data under certain use cases.

Special modifications such as WeakSets and WeakMaps provide more options for the developer and are handy for memory management.

#javascript #js

1 Likes101.85 GEEK