Map and WeakMap objects were introduced with the ES6 release.
They were developed to be used as an alternative to Objects as key-value storage.
Unlike Objects, that are limited to having a String or a Symbol as a key, Maps allow you to use any value as a key, WeakMaps - only objects.
A Map is an object in JavaScript that contains key-value pairs.
It remembers the insertion order of keys and allows you to use any value either as a key or as a value.
A new Map object can be created using the Map
constructor:
// Create a new Map
const users = new Map();
An element can be added to the Map by using the set(key, value)
function.
Returns the Map object:
// String as a key
users.set("John", { address: "John's Address" });
// Object as a key
const obj = { name: "Michael" };
users.set(obj, { address: "Michael's Address"});
// Function as a key
const func = () => "Andrew";
users.set(func, { address: "Andrew's Address"});
// NaN as a key
users.set(NaN, { address: "NaN's Address"});
An element can be retrieved from the Map by using get(key)
function.
Returns the value that is associated with the key or undefined:
users.get("John"); // { address: "John's Address" }
users.get(obj); // { address: "Michael's Address" }
users.get(func); // { address: "Andrew's Address" }
users.get(NaN); // { address: "NaN's Address" }
users.get("Not existing item"); // undefined
To check whether a value has been linked to the key, a has(key)
function can be used.
Returns true or false:
users.has("John"); // true
users.has(obj); // true
users.has(func); // true
users.has(NaN); // true
users.has("Not existing item"); // false
An element can be removed from the Map by using the delete(key)
function.
Returns true if an element has been removed, false if it does not exist:
users.delete("John"); // true
users.delete(obj); // true
users.delete(func); // true
users.delete(NaN); // true
users.delete("Not existing item"); // false
To remove all key-value pairs from the Map object, a clear()
method can be used:
users.clear();
console.log(users); // Map {}
To determine the size of the Map, an instance property size
can be accessed.
Returns the number of key-value pairs:
const projects = new Map();
projects.set("project1", { deadline: "2020-12-31" });
projects.set("project2", { deadline: "2020-12-31" });
projects.size; // 2
To get all keys of the Map, a keys()
method is used.
Returns an Iterator object that contains all keys in an insertion order:
projects.keys(); // [Map Iterator] { "project1", "project2" }
To get all values of the Map, a values()
method is used.
Returns an Iterator object that contains all values in an insertion order:
// [Map Iterator] {
// { deadline: "2020-12-31" },
// { deadline: "2020-12-31" }
// }
projects.values();
To get all entries of the Map, the entries()
function is used.
Returns an Iterator object that containing an array of all [key, value] pairs in an insertion order:
// [Map Entries] {
// [ "project1", { deadline: "2020-12-31" } ],
// [ "project2", { deadline: "2020-12-31" } ]
// }
projects.entries();
To iterate over all key-value pairs, a forEach(callbackFn[, thisArg])
method is used:
Calls callbackFn for each key-value pair in insertion order. If a thisArg is specified, it is used as this value for each callback:
// "project1" { deadline: "2020-12-31" }
// "project2" { deadline: "2020-12-31" }
projects.forEach((value, key) => {
console.log(key, value);
});
An Object is a collection of properties, defined as key-value pairs.
At first glance, it may seem that there are really no differences between Map and Object, but this assumption is completely wrong.
Objects are similar to Maps, both allow you to store, retrieve, and delete values, but there are important differences:
size
property, an Object does not provide an easy way to do thisA WeakMap is similar to the Map with some important differences:
An example of a WeakMap:
const users = new WeakMap();
const john = { id: 1, name: "John" };
const andrew = { id: 2, name: "Andrew" };
users.set(john, { address: "John's Address"}); // OK
users.set(andrew, { address: "Andrew's Address"}); // OK
// TypeError: Invalid value used as weak map key
users.set("text", "Hello, World!");
If an object is used as a key of the WeakMap and there are no other references to this object, it is automatically garbage-collected (removed from the WeakMap and the memory):
const users = new WeakMap();
let john = { id: 1, name: "John" };
users.set(john, { address: "John's Address"}); // OK
// "John" is removed both, from the memory and the WeakMap
john = null;
Compare it to the Map:
const users = new Map();
let john = { id: 1, name: "John" };
users.set(john, { address: "John's Address"}); // OK
// John is removed from the memory, but not from the Map
john = null;
// Map {{ id: 1, name: "John" } => { address: "John's Address" } }
console.log(users);
WeakMap only supports the following methods:
get(key)
set(key)
has(key)
delete(key)
This means that it is impossible to iterate or loop the keys, values and entries, calculate the size or delete all values of a WeakMap.
To understand that restrictions are necessary, we need to know how the garbage-collection works in JavaScript.
When an object has lost all its references, it has to be garbage-collected automatically, but we do not know when exactly when it will happen.
It is decided by the JavaScript engine. The cleanup can be done immediately or after some time.
Since an object is also deleted from the WeakMap, we do not exactly know what elements it contains, so we cannot calculate the size or iterate over its elements.
WeakMaps can be extremely useful for storing information about the keys, which is only valuable if the key has not been garbage-collected.
They offer a possibility to extend objects from outside without interfering with garbage collection.
Whenever you need to extend an object but cannot do so because it is sealed or comes from an external source, a WeakMap can be applied.
Consider the following example:
const cache = new WeakMap();
function getData(key) {
if (cache.has(key)) {
console.log("Return from cache");
return cache.get(key);
} else {
console.log("Calculate, cahce and return");
// Calculate the value
const result = 0;
// Cache the result
cache.set(key, result);
// Return it
return result;
}
}
let user = { name: "John" };
// "Calculate, cahce and return"
getData(user);
// "Return from cache"
getData(user);
// "Return from cache"
getData(user);
// When the object is not needed anymore
// It will be removed from the cache as well
user = null;
In the example above, we have implemented a simple cache that holds the objects until they are garbage-collected.
If we would use Map instead of WeakMap, we could end up with a memory leak, because even if the object is garbage-collected, it still will be stored in the cache.
Today we learned what is and how to work with Map and WeakMap in JavaScript.
When it comes to storing data, both can be considered as alternatives to Objects, because they are optimized to provide a better performance when adding/removing entries frequently.
It is definitely worth trying them and seeing what advantages can be achieved.