Understanding Map and Set Objects in JavaScript

Understanding Map and Set Objects in JavaScript

In this JavaScript tutorial, you will go over the Map and Set objects, what makes them similar or different to Objects and Arrays, the properties and methods available to them, and examples of some practical uses.

In JavaScript, developers often spend a lot of time deciding the correct data structure to use. This is because choosing the correct data structure can make it easier to manipulate that data later on, saving time and making code easier to comprehend. The two predominant data structures for storing collections of data are Objects and Arrays (a type of object). Developers use Objects to store key/value pairs and Arrays to store indexed lists. However, to give developers more flexibility, the ECMAScript 2015 specification introduced two new types of iterable objects: Maps, which are ordered collections of key/value pairs, and Sets, which are collections of unique values.

In this article, you will go over the Map and Set objects, what makes them similar or different to Objects and Arrays, the properties and methods available to them, and examples of some practical uses.

Maps

A Map is a collection of key/value pairs that can use any data type as a key and can maintain the order of its entries. Maps have elements of both Objects (a unique key/value pair collection) and Arrays (an ordered collection), but are more similar to Objects conceptually. This is because, although the size and order of entries is preserved like an Array, the entries themselves are key/value pairs like Objects.

Maps can be initialized with the new Map() syntax:

const map = new Map()

This gives us an empty Map:

OutputMap(0) {}

Adding Values to a Map

You can add values to a map with the set() method. The first argument will be the key, and the second argument will be the value.

The following adds three key/value pairs to map:

map.set('firstName', 'Luke')
map.set('lastName', 'Skywalker')
map.set('occupation', 'Jedi Knight')

Here we begin to see how Maps have elements of both Objects and Arrays. Like an Array, we have a zero-indexed collection, and we can also see how many items are in the Map by default. Maps use the => syntax to signify key/value pairs as key => value:

OutputMap(3)
0: {"firstName" => "Luke"}
1: {"lastName" => "Skywalker"}
2: {"occupation" => "Jedi Knight"}

This example looks similar to a regular object with string-based keys, but we can use any data type as a key with Maps.

In addition to manually setting values on a Map, we can also initialize a Map with values already. We do this using an Array of Arrays containing two elements that are each key/value pairs, which looks like this:

[ [ 'key1', 'value1'], ['key2', 'value2'] ]

Using the following syntax, we can recreate the same Map:

const map = new Map([
  ['firstName', 'Luke'],
  ['lastName', 'Skywalker'],
  ['occupation', 'Jedi Knight'],
])

Note: This example uses trailing commas, also referred to as dangling commas. This is a JavaScript formatting practice in which the final item in a series when declaring a collection of data has a comma at the end. Though this formatting choice can be used for cleaner diffs and easier code manipulation, whether to use it or not is a matter of preference.

Incidentally, this syntax is the same as the result of calling Object.entries() on an Object. This provides a ready-made way to convert an Object to a Map, as shown in the following code block:

const luke = {
  firstName: 'Luke',
  lastName: 'Skywalker',
  occupation: 'Jedi Knight',
}

const map = new Map(Object.entries(luke))

Alternatively, you can turn a Map back into an Object or an Array with a single line of code.

The following converts a Map to an Object:

const obj = Object.fromEntries(map)

This will result in the following value of obj:

Output{firstName: "Luke", lastName: "Skywalker", occupation: "Jedi Knight"}

Now, let’s convert a Map to an Array:

const arr = Array.from(map)

This will result in the following Array for arr:

Output[ ['firstName', 'Luke'],
  ['lastName', 'Skywalker'],
  ['occupation', 'Jedi Knight'] ]

Map Keys

Maps accept any data type as a key, and do not allow duplicate key values. We can demonstrate this by creating a map and using non-string values as keys, as well as setting two values to the same key.

First, let’s initialize a map with non-string keys:

const map = new Map()

map.set('1', 'String one')
map.set(1, 'This will be overwritten')
map.set(1, 'Number one')
map.set(true, 'A Boolean')

This example will override the first key of 1 with the subsequent one, and it will treat '1' the string and 1 the number as unique keys:

Output0: {"1" => "String one"}
1: {1 => "Number one"}
2: {true => "A Boolean"}

Although it is a common belief that a regular JavaScript Object can already handle Numbers, booleans, and other primitive data types as keys, this is actually not the case, because Objects change all keys to strings.

As an example, initialize an object with a numerical key and compare the value for a numerical 1 key and a stringified "1" key:

// Initialize an object with a numerical key
const obj = { 1: 'One' }

// The key is actually a string
obj[1] === obj['1']  // true

This is why if you attempt to use an Object as a key, it will print out the string object Object instead.

As an example, create an Object and then use it as the key of another Object:

// Create an object
const objAsKey = { foo: 'bar' }

// Use this object as the key of another object
const obj = {
  [objAsKey]: 'What will happen?'
} 

This will yield the following:

Output{[object Object]: "What will happen?"}

This is not the case with Map. Try creating an Object and setting it as the key of a Map:

// Create an object
const objAsKey = { foo: 'bar' }

const map = new Map()

// Set this object as the key of a Map
map.set(objAsKey, 'What will happen?')

The key of the Map element is now the object we created.

Outputkey: {foo: "bar"}
value: "What will happen?"

There is one important thing to note about using an Object or Array as a key: the Map is using the reference to the Object to compare equality, not the literal value of the Object. In JavaScript {} === {} returns false, because the two Objects are not the same two Objects, despite having the same (empty) value.

That means that adding two unique Objects with the same value will create a Map with two entries:

// Add two unique but similar objects as keys to a Map
map.set({}, 'One')
map.set({}, 'Two')

This will yield the following:

OutputMap(2) {{…} => "One", {…} => "Two"}

But using the same Object reference twice will create a Map with one entry.

// Add the same exact object twice as keys to a Map
const obj = {}

map.set(obj, 'One')
map.set(obj, 'Two')

Which will result in the following:

OutputMap(1) {{…} => "Two"}

The second set() is updating the same exact key as the first, so we end up with a Map that only has one value.

Getting and Deleting Items from a Map

One of the disadvantages of working with Objects is that it can be difficult to enumerate them, or work with all the keys or values. The Map structure, by contrast, has a lot of built-in properties that make working with their elements more direct.

We can initialize a new Map to demonstrate the following methods and properties: delete(), has(), get(), and size.

// Initialize a new Map
const map = new Map([
  ['animal', 'otter'],
  ['shape', 'triangle'],
  ['city', 'New York'],
  ['country', 'Bulgaria'],
])

Use the has() method to check for the existence of an item in a map. has() will return a Boolean.

// Check if a key exists in a Map
map.has('shark') // false
map.has('country') // true

Use the get() method to retrieve a value by key.

// Get an item from a Map
map.get('animal') // "otter"

One particular benefit Maps have over Objects is that you can find the size of a Map at any time, like you can with an Array. You can get the count of items in a Map with the size property. This involves fewer steps than converting an Object to an Array to find the length.

// Get the count of items in a Map
map.size // 4

Use the delete() method to remove an item from a Map by key. The method will return a Boolean—true if an item existed and was deleted, and false if it did not match any item.

// Delete an item from a Map by key
map.delete('city') // true

This will result in the following Map:

OutputMap(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}

Finally, a Map can be cleared of all values with map.clear().

// Empty a Map
map.clear()

This will yield:

OutputMap(0) {}

Keys, Values, and Entries for Maps

Objects can retrieve keys, values, and entries by using the properties of the Object constructor. Maps, on the other hand, have prototype methods that allow us to get the keys, values, and entries of the Map instance directly.

The keys(), values(), and entries() methods all return a MapIterator, which is similar to an Array in that you can use for...of to loop through the values.

Here is another example of a Map, which we can use to demonstrate these methods:

const map = new Map([
  [1970, 'bell bottoms'],
  [1980, 'leg warmers'],
  [1990, 'flannel'],
])

The keys() method returns the keys:

map.keys()

OutputMapIterator {1970, 1980, 1990}

The values() method returns the values:

map.values()

OutputMapIterator {"bell bottoms", "leg warmers", "flannel"}

The entries() method returns an array of key/value pairs:

map.entries()

OutputMapIterator {1970 => "bell bottoms", 1980 => "leg warmers", 1990 => "flannel"}

Iteration with Map

Map has a built-in forEach method, similar to an Array, for built-in iteration. However, there is a bit of a difference in what they iterate over. The callback of a Map’s forEach iterates through the value, key, and map itself, while the Array version iterates through the item, index, and array itself.

// Map 
Map.prototype.forEach((value, key, map) = () => {})

// Array
Array.prototype.forEach((item, index, array) = () => {})

This is a big advantage for Maps over Objects, as Objects need to be converted with keys(), values(), or entries(), and there is not a simple way to retrieve the properties of an Object without converting it.

To demonstrate this, let’s iterate through our Map and log the key/value pairs to the console:

// Log the keys and values of the Map with forEach
map.forEach((value, key) => {
  console.log(`${key}: ${value}`)
})

This will give:

Output1970: bell bottoms
1980: leg warmers
1990: flannel

Since a for...of loop iterates over iterables like Map and Array, we can get the exact same result by destructuring the array of Map items:

// Destructure the key and value out of the Map item
for (const [key, value] of map) {
  // Log the keys and values of the Map with for...of
  console.log(`${key}: ${value}`)
}

Map Properties and Methods

The following table shows a list of Map properties and methods for quick reference:

When to Use Map

Summing up, Maps are similar to Objects in that they hold key/value pairs, but Maps have several advantages over objects:

  • Size - Maps have a size property, whereas Objects do not have a built-in way to retrieve their size.
  • Iteration - Maps are directly iterable, whereas Objects are not.
  • Flexibility - Maps can have any data type (primitive or Object) as the key to a value, while Objects can only have strings.
  • Ordered - Maps retain their insertion order, whereas objects do not have a guaranteed order.

Due to these factors, Maps are a powerful data structure to consider. However, Objects haves some important advantages as well:

  • JSON - Objects work flawlessly with JSON.parse() and JSON.stringify(), two essential functions for working with JSON, a common data format that many REST APIs deal with.
  • Working with a single element - Working with a known value in an Object, you can access it directly with the key without the need to use a method, such as Map’s get().

This list will help you decide if a Map or Object is the right data structure for your use case.

Set

A Set is a collection of unique values. Unlike a Map, a Set is conceptually more similar to an Array than an Object, since it is a list of values and not key/value pairs. However, Set is not a replacement for Arrays, but rather a supplement for providing additional support for working with duplicated data.

You can initialize Sets with the new Set() syntax.

const set = new Set()

This gives us an empty Set:

OutputSet(0) {}

Items can be added to a Set with the add() method. (This is not to be confused with the set() method available to Map, although they are similar.)

// Add items to a Set
set.add('Beethoven')
set.add('Mozart')
set.add('Chopin')

Since Sets can only contain unique values, any attempt to add a value that already exists will be ignored.

set.add('Chopin') // Set will still contain 3 unique values

Note: The same equality comparison that applies to Map keys applies to Set items. Two objects that have the same value but do not share the same reference will not be considered equal.

You can also initialize Sets with an Array of values. If there are duplicate values in the array, they will be removed from the Set.

// Initialize a Set from an Array
const set = new Set(['Beethoven', 'Mozart', 'Chopin', 'Chopin'])

OutputSet(3) {"Beethoven", "Mozart", "Chopin"}

Conversely, a Set can be converted into an Array with one line of code:

const arr = [...set]

Output(3) ["Beethoven", "Mozart", "Chopin"]

Set has many of the same methods and properties as Map, including delete(), has(), clear(), and size.

// Delete an item
set.delete('Beethoven') // true

// Check for the existence of an item
set.has('Beethoven') // false

// Clear a Set
set.clear()

// Check the size of a Set
set.size // 0

Note that Set does not have a way to access a value by a key or index, like Map.get(key) or arr[index].

Keys, Values, and Entries for Sets

Map and Set both have keys(), values(), and entries() methods that return an Iterator. However, while each one of these methods have a distinct purpose in Map, Sets do not have keys, and therefore keys are an alias for values. This means that keys() and values() will both return the same Iterator, and entries() will return the value twice. It makes the most sense to only use values() with Set, as the other two methods exist for consistency and cross-compatibility with Map.

const set = new Set([1, 2, 3])
// Get the values of a set
set.values()

OutputSetIterator {1, 2, 3}

Iteration with Set

Like Map, Set has a built-in forEach() method. Since Sets don’t have keys, the first and second parameter of the forEach() callback return the same value, so there is no use case for it outside of compatibility with Map. The parameters of forEach() are (value, key, set).

Both forEach() and for...of can be used on Set. First, let’s look at forEach() iteration:

const set = new Set(['hi', 'hello', 'good day'])

// Iterate a Set with forEach
set.forEach((value) => console.log(value))

Then we can write the for...of version:

// Iterate a Set with for...of
for (const value of set) {  
    console.log(value);
}

Both of these strategies will yield the following:

Outputhi
hello
good day

Set Properties and Methods

The following table shows a list of Set properties and methods for quick reference:

When to Use Set

Set is a useful addition to your JavaScript toolkit, particularly for working with duplicate values in data.

In a single line, we can create a new Array without duplicate values from an Array that has duplicate values.

const uniqueArray = [ ...new Set([1, 1, 2, 2, 2, 3])] // (3) [1, 2, 3]

This will give:

Output(3) [1, 2, 3]

Set can be used for finding the union, intersection, and difference between two sets of data. However, Arrays have a significant advantage over Sets for additional manipulation of the data due to the sort(), map(), filter(), and reduce() methods, as well as direct compatibility with JSON methods.

Conclusion

In this article, you learned that a Map is a collection of ordered key/value pairs, and that a Set is a collection of unique values. Both of these data structures add additional capabilities to JavaScript and simplify common tasks such as finding the length of a key/value pair collection and removing duplicate items from a data set, respectively. On the other hand, Objects and Arrays have been traditionally used for data storage and manipulation in JavaScript, and have direct compatibility with JSON, which continues to make them the most essential data structures, especially for working with REST APIs. Maps and Sets are primarily useful as supporting data structures for Objects and Arrays.

Originally published by Tania Rascia at https://www.digitalocean.com

Hire Dedicated eCommerce Web Developers | Top eCommerce Web Designers

Hire Dedicated eCommerce Web Developers | Top eCommerce Web Designers

Build your eCommerce project by hiring our expert eCommerce Website developers. Our Dedicated Web Designers develop powerful & robust website in a short span of time.

Build your eCommerce project by hiring our expert eCommerce Website developers. Our Dedicated Web Designers develop powerful & robust website in a short span of time.

Hire Now: https://bit.ly/394wdOx

Mobile App Development Company India | Ecommerce Web Development Company India

Mobile App Development Company India | Ecommerce Web Development Company India

Best Mobile App Development Company India, WebClues Global is one of the leading web and mobile app development company. Our team offers complete IT solutions including Cross-Platform App Development, CMS & E-Commerce, and UI/UX Design.

We are custom eCommerce Development Company working with all types of industry verticals and providing them end-to-end solutions for their eCommerce store development.

Know more about Top E-Commerce Web Development Company

JavaScript developers should you be using Web Workers?

JavaScript developers should you be using Web Workers?

Do you think JavaScript developers should be making more use of Web Workers to shift execution off of the main thread?

Originally published by David Gilbertson at https://medium.com

So, Web Workers. Those wonderful little critters that allow us to execute JavaScript off the main thread.

Also known as “no, you’re thinking of Service Workers”.

Photo by Caleb Jones on Unsplash

Before I get into the meat of the article, please sit for a lesson in how computers work:

Understood? Good.

For the red/green colourblind, let me explain. While a CPU is doing one thing, it can’t be doing another thing, which means you can’t sort a big array while a user scrolls the screen.

This is bad, if you have a big array and users with fingers.

Enter, Web Workers. These split open the atomic concept of a ‘CPU’ and allow us to think in terms of threads. We can use one thread to handle user-facing work like touch events and rendering the UI, and different threads to carry out all other work.

Check that out, the main thread is green the whole way through, ready to receive and respond to the gentle caress of a user.

You’re excited (I can tell), if we only have UI code on the main thread and all other code can go in a worker, things are going to be amazing (said the way Oprah would say it).

But cool your jets for just a moment, because websites are mostly about the UI — it’s why we have screens. And a lot of a user’s interactions with your site will be tapping on the screen, waiting for a response, reading, tapping, looking, reading, and so on.

So we can’t just say “here’s some JS that takes 20ms to run, chuck it on a thread”, we must think about where that execution time exists in the user’s world of tap, read, look, read, tap…

I like to boil this down to one specific question:

Is the user waiting anyway?

Imagine we have created some sort of git-repository-hosting website that shows all sorts of things about a repository. We have a cool feature called ‘issues’. A user can even click an ‘issues’ tab in our website to see a list of all issues relating to the repository. Groundbreaking!

When our users click this issues tab, the site is going to fetch the issue data, process it in some way — perhaps sort, or format dates, or work out which icon to show — then render the UI.

Inside the user’s computer, that’ll look exactly like this.

Look at that processing stage, locking up the main thread even though it has nothing to do with the UI! That’s terrible, in theory.

But think about what the human is actually doing at this point. They’re waiting for the common trio of network/process/render; just sittin’ around with less to do than the Bolivian Navy.

Because we care about our users, we show a loading indicator to let them know we’ve received their request and are working on it — putting the human in a ‘waiting’ state. Let’s add that to the diagram.

Now that we have a human in the picture, we can mix in a Web Worker and think about the impact it will have on their life:

Hmmm.

First thing to note is that we’re not doing anything in parallel. We need the data from the network before we process it, and we need to process the data before we can render the UI. The elapsed time doesn’t change.

(BTW, the time involved in moving data to a Web Worker and back is negligible: 1ms per 100 KB is a decent rule of thumb.)

So we can move work off the main thread and have a page that is responsive during that time, but to what end? If our user is sitting there looking at a spinner for 600ms, have we enriched their experience by having a responsive screen for the middle third?

No.

I’ve fudged these diagrams a little bit to make them the gorgeous specimens of graphic design that they are, but they’re not really to scale.

When responding to a user request, you’ll find that the network and DOM-manipulating part of any given task take much, much longer than the pure-JS data processing part.

I saw an article recently making the case that updating a Redux store was a good candidate for Web Workers because it’s not UI work (and non-UI work doesn’t belong on the main thread).

Chucking the data processing over to a worker thread sounds sensible, but the idea struck me as a little, umm, academic.

First, let’s split instances of ‘updating a store’ into two categories:

  1. Updating a store in response to a user interaction, then updating the UI in response to the data change
  2. Not that first one

If the first scenario, a user taps a button on the screen — perhaps to change the sort order of a list. The store updates, and this results in a re-rendering of the DOM (since that’s the point of a store).

Let me just delete one thing from the previous diagram:

In my experience, it is rare that the store-updating step goes beyond a few dozen milliseconds, and is generally followed by ten times that in DOM updating, layout, and paint. If I’ve got a site that’s taking longer than this, I’d be asking questions about why I have so much data in the browser and so much DOM, rather than on which thread I should do my processing.

So the question we’re faced with is the same one from above: the user tapped something on the screen, we’re going to work on that request for hopefully less than a second, why would we want to make the screen responsive during that time?

OK what about the second scenario, where a store update isn’t in response to a user interaction? Performing an auto-save, for example — there’s nothing more annoying than an app becoming unresponsive doing something you didn’t ask it to do.

Actually there’s heaps of things more annoying than that. Teens, for example.

Anyhoo, if you’re doing an auto-save and taking 100ms to process data client-side before sending it off to a server, then you should absolutely use a Web Worker.

In fact, any ‘background’ task that the user hasn’t asked for, or isn’t waiting for, is a good candidate for moving to a Web Worker.

The matter of value

Complexity is expensive, and implementing Web Workers ain’t cheap.

If you’re using a bundler — and you are — you’ll have a lot of reading to do, and probably npm packages to install. If you’ve got a create-react-app app, prepare to eject (and put aside two days twice a year to update 30 different packages when the next version of Babel/Redux/React/ESLint comes out).

Also, if you want to share anything fancier than plain data between a worker and the main thread you’ve got some more reading to do (comlink is your friend).

What I’m getting at is this: if the benefit is real, but minimal, then you’ve gotta ask if there’s something else you could spend a day or two on with a greater benefit to your users.

This thinking is true of everything, of course, but I’ve found that Web Workers have a particularly poor benefit-to-effort ratio.

Hey David, why you hate Web Workers so bad?

Good question.

This is a doweling jig:

I own a doweling jig. I love my doweling jig. If I need to drill a hole into the end of a piece of wood and ensure that it’s perfectly perpendicular to the surface, I use my doweling jig.

But I don’t use it to eat breakfast. For that I use a spoon.

Four years ago I was working on some fancy animations. They looked slick on a fast device, but janky on a slow one. So I wrote fireball-js, which executes a rudimentary performance benchmark on the user’s device and returns a score, allowing me to run my animations only on devices that would render them smoothly.

Where’s the best spot to run some CPU intensive code that the user didn’t request? On a different thread, of course. A Web Worker was the correct tool for the job.

Fast forward to 2019 and you’ll find me writing a routing algorithm for a mapping application. This requires parsing a big fat GeoJSON map into a collection of nodes and edges, to be used when a user asks for directions. The processing isn’t in response to a user request and the user isn’t waiting on it. And so, a Web Worker is the correct tool for the job.

It was only when doing this that it dawned on me: in the intervening quartet of years, I have seen exactly zero other instances where Web Workers would have improved the user experience.

Contrast this with a recent resurgence in Web Worker wonderment, and combine that contrast with the fact that I couldn’t think of anything else to write about, then concatenate that combined contrast with my contrarian character and you’ve got yourself a blog post telling you that maybe Web Workers are a teeny-tiny bit overhyped.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

An Introduction to Web Workers

JavaScript Web Workers: A Beginner’s Guide

Using Web Workers to Real-time Processing

How to use Web Workers in Angular app

Using Web Workers with Angular CLI