Javascript Map/Set [ES2015] tutorial
Arrays are not the only collections that handle collections of data in JavaScript. In this chapter, we will learn about Map, a collection of type Map, and Set, a collection of type Set, which were introduced in ES2015.
Map #
Map is a built-in object for handling collections of type map. A map is an abstract data type consisting of a combination of keys and values. In the context of other programming languages, it is sometimes called a dictionary, hash map, or associative array.
create and initialize a map #
You can create a new map by new
ing a Map
object.
A newly created map does not have anything in it.
Therefore, the size
property, which returns the size of the map, will return 0
.
const map = new Map();
console.log(map.size); // => 0
When you initialize a Map
object with new
, you can pass the initial value to the constructor.
What you can pass as constructor argument is an array of entries.
An entry is a single key/value combination represented as an array of the form [key, value]
.
In the following code, an array of entries (array) is passed to Map as the initial value.
const map = new Map([["key1", "value1"], ["key2", "value2"]]);
// initialized with two entries
console.log(map.size); // => 2
Adding and removing elements #
You can add new elements to a Map
with the set
method, and retrieve the added elements with the get
method.
The set
method adds an element with a specific key and value to the map.
The set
method adds an element with a specific key and value to the map, but if the set
method is called multiple times with the same key, it will be overwritten by the value added later.
The get
method retrieves the value associated with a specific key.
The get
method retrieves the value associated with a particular key, and the has
method checks if you have the value associated with a particular key.
const map = new Map();
// Add a new element
map.set("key", "value1");
console.log(map.size); // => 1
console.log(map.get("key")); // => "value1"
// Overwrite the element
map.set("key", "value2");
console.log(map.get("key")); // => "value2"
// check for the existence of the key
console.log(map.has("key")); // => true
console.log(map.has("foo")); // => false
The delete
method deletes an element from the map.
The key passed to the delete
method and the value associated with that key will be deleted from the map.
There is also a clear
method for deleting all elements that the map has.
const map = new Map();
map.set("key1", "value1");
map.set("key2", "value2");
console.log(map.size); // => 2
map.delete("key1");
console.log(map.size); // => 1
map.clear();
console.log(map.size); // => 0
Iterating over a map #
There are three methods for enumerating the elements of a map: forEach
, keys
, values
, and entries
.
The forEach
method iterates through all the elements of the map in the order of their insertion into the map.
The forEach
method iterates through all elements of the map in the order of insertion into the map. The callback function is passed three arguments: values, keys, and the map.
It is similar to the forEach
method for arrays, but instead of an index, a key is passed.
This is because arrays use indexes to identify elements, while maps use keys to identify elements.
const map = new Map([["key1", "value1"], ["key2", "value2"]]);
const results = [];
map.forEach((value, key) => {
results.push(`${key}:${value}`);
});
console.log(results); // => ["key1:value1", "key2:value2"].
The keys
method returns an Iterator object with the keys of all the elements the map has, ordered by insertion.
Similarly, the values
method returns an Iterator object containing the values of all the elements of the map, ordered by insertion.
Their return value is an Iterator object, not an array.
Therefore, iterating with the for. . of
statement, or passed to the Array.from
method to be converted to an array for use.
const map = new Map([["key1", "value1"], ["key2", "value2"]]);
const keys = [];
// iterate over the return value (Iterator) of the keys method
for (const key of map.keys()) {
keys.push(key);
}
console.log(keys); // => ["key1", "key2"].
// create an array from the return value of the keys method (Iterator)
const keysArray = Array.from(map.keys());
console.log(keysArray); // => ["key1", "key2"].
The entries
method returns an Iterator object containing all the elements of the map, arranged in insertion order as entries.
As mentioned earlier, an entry is an array such as [key, value]
.
Therefore, the key and value can be easily retrieved from the entry by using split array assignment.
const map = new Map([["key1", "value1"], ["key2", "value2"]]);
const entries = [];
for (const [key, value] of map.entries()) {
entries.push(`${key}:${value}`);
}
console.log(entries); // => ["key1:value1", "key2:value2"].
Also, since the map itself is an iterable object, you can use the for. . of
statement.
Iterating over a map with a for. . of
statements, all elements are iterated over in order of insertion as entries.
In other words, you get the same result as when iterating over the return value of the entries
method.
const map = new Map([["key1", "value1"], ["key2", "value2"]]);
const results = [];
for (const [key, value] of map) {
results.push(`${key}:${value}`);
}
console.log(results); // => ["key1:value1", "key2:value2"].
Object and Map as Maps #
Until the introduction of Map in ES2015, Object has been used to achieve the map type in JavaScript. Map and Object are very similar in that they use something as a key to access a value. However, there are some problems with Object as a map.
There is a risk of unintended mappings due to properties inherited from Object's prototype object. Since it has data as properties, it can only be used as keys for strings or symbols. Since Object has a prototype object, some of its properties exist from the time it is initialized. If you use Object as a map, you will have a problem when you try to use a key with the same name as the property (see "Checking for the existence of properties" in the chapter "Objects" for details).
For example, the string constructor conflicts with the Object.prototype.constructor property. Therefore, there is a risk of unintended mappings by using a string like constructor as a key of an object.
const map = {};
// Make sure the map has a key
function has(key) {
return typeof map[key] ! == "undefined";
}
console.log(has("foo")); // => false
// Object property exists
console.log(has("constructor")); // => true
The problem with objects used as maps has been avoided by initializing the Object instance as Object.create(null) (see "Objects that do not inherit from Object.prototype" in the chapter "Prototype Objects" for details). ).
ES2015 introduces Map, which fundamentally solves these problems. Map stores data in a different mechanism than properties. Map stores data in a different way than properties, so that keys do not conflict with methods and properties of Map prototypes. Map can also use any object as a map key.
Other advantages of Map are as follows
Easy to know the size of the map Easily enumerate the elements of a map Different mappings can be done for different references by using objects as keys For example, when creating a system like a shopping cart, you can use Map to map the product object to the number of orders, as shown below.
// Class to represent a shopping cart
class ShoppingCart {
constructor() {
// Map with products and their number
this.items = new Map();
}
// add an item to the cart
addItem(item) {
// If there is no `item`, it returns `undefined`, so use the nullish coalescing operator (`? `) to set the default value to `0`.
const count = this.items.get(item) ? 0;
this.items.set(item, count + 1);
}
// return the total price in the cart
getTotalPrice() {
return Array.from(this.items).reduce((total, [item, count]) => {
return total + item.price * count;
}, 0);
}
// return the contents of the cart as a string
toString() {
return Array.from(this.items).map(([item, count]) => {
return `${item.name}:${count}`;
}).join(",");
}
}
const shoppingCart = new ShoppingCart();
// Product list
const shopItems = [.
{ name: "mandarin", price: 100 }
{ name: "apple", price: 200 },
];
// Add the items to the cart
shoppingCart.addItem(shopItems[0]);
shoppingCart.addItem(shopItems[0]);
shoppingCart.addItem(shopItems[1]);
// Display the total amount
console.log(shoppingCart.getTotalPrice()); // => 400
// Display the contents of the cart
console.log(shoppingCart.toString()); // => "Oranges:2,Apples:1"
Many of the problems that occur when using Object as a map can be solved by using a Map object, but a Map can not always replace an Object. As a map, Object has the following advantages
It is easy to create because of its literal representation. Easy to convert to JSON using the JSON.stringify function, since there is a default JSON representation Many functions, whether native APIs or external libraries, are designed to be passed Objects as maps. In the following example, a POST request is sent to the server after receiving the submit event of the login form. To send the JSON string to the server, we use the JSON.stringify function. For this purpose, we have created a map of Objects to hold the input contents of the form. For a simple map like this, it is more appropriate to use Object.
// Function to receive URL and Object map and send POST request function sendPOSTRequest(url, data) { // send a POST request using XMLHttpRequest const httpRequest = new XMLHttpRequest(); httpRequest.setRequestHeader("Content-Type", "application/json"); httpRequest.send(JSON.stringify(data)); httpRequest.open("POST", url); }
// Function to receive the submit event of a form
function onLoginFormSubmit(event) {
const form = event.target;
const data = {
userName: form.elements.userName,
password: form.elements.password,
};
sendPOSTRequest("/api/login", data);
}