JavaScript‌ ‌Map‌ ‌and‌ ‌Set‌ ‌

While‌ ‌working‌ ‌with‌ ‌‌JavaScript‌,‌ ‌developers‌ ‌waste‌ ‌a‌ ‌lot‌ ‌of‌ ‌time‌ ‌on‌ ‌choosing‌ the‌ ‌right‌ ‌data‌ ‌structure.‌

Objects‌‌ ‌and‌ ‌‌ Arrays‌‌ ‌are‌ ‌the‌ ‌primary‌ ‌data‌ ‌structures‌ ‌used‌ ‌to‌ ‌store‌ ‌collections‌ ‌of‌ ‌data.‌ ‌Objects‌ ‌are‌ ‌used‌ ‌for‌ ‌storing‌ ‌key/value‌ ‌pairs,‌ ‌and‌ ‌arrays-‌ ‌for‌ ‌indexed‌ ‌lists.‌ ‌ ‌ To‌ ‌make‌ ‌developers’‌ ‌life‌ ‌easier,‌ ‌ECMAScript‌ ‌2015‌ ‌represented‌ ‌two‌ ‌new‌ ‌kinds‌ ‌ of‌ ‌‌iterable‌‌ ‌objects:‌ ‌Maps‌ ‌and‌ ‌Sets.‌

‌Maps‌ ‌

A‌ ‌map‌ ‌is‌ ‌a‌ ‌key-value‌ ‌pairs‌ ‌collection‌ ‌that‌ ‌can‌ ‌apply‌ ‌any‌ ‌data‌ ‌type‌ ‌as‌ ‌a‌ ‌key,‌ ‌ maintaining‌ ‌the‌ ‌sequence‌ ‌of‌ ‌its‌ ‌entries.‌

Maps‌ ‌contain‌ ‌both‌ ‌elements‌ ‌of‌ ‌objects‌ ‌and‌ ‌array.‌ ‌But,‌ ‌conceptually,‌ ‌they‌ ‌are‌ ‌similar‌ ‌to‌ ‌objects.‌

The‌ ‌primary‌ ‌distinction‌ ‌is‌ ‌that‌ ‌maps‌ ‌allow‌ ‌any‌ ‌type‌ ‌of‌ ‌keys.‌

The‌ ‌main‌ ‌properties‌ ‌and‌ ‌methods‌ ‌of‌ ‌maps‌ ‌are‌ ‌the‌ ‌following:‌

  • new‌ ‌Map()‌‌ ‌‌–‌ ‌creating‌ ‌the‌ ‌map.‌
  • map.set(key,‌ ‌value)‌‌ ‌–‌ ‌storing‌ ‌the‌ ‌value‌ ‌by‌ ‌the‌ ‌key.‌
  • map.get(key)‌‌ ‌–‌ ‌returning‌ ‌the‌ ‌value‌ ‌by‌ ‌the‌ ‌key,‌ ‌‌undefined‌‌ ‌‌if‌ ‌no‌ ‌‌key‌‌ ‌exists‌ ‌in‌ ‌the‌ ‌map.‌
  • map.has(key)‌‌ ‌–‌ ‌returning‌‌ ‌‌true‌‌ ‌if‌ ‌there‌ ‌exists‌ ‌a‌ ‌‌key‌,‌ ‌and‌ ‌‌false‌‌ ‌otherwise.‌
  • map.delete(key)‌‌ ‌–‌ ‌removing‌ ‌the‌ ‌value‌ ‌by‌ ‌the‌ ‌key.‌
  • map.clear()‌‌ ‌–‌ ‌removing‌ ‌everything‌ ‌from‌ ‌the‌ ‌map.‌
  • map.size‌‌ ‌–‌ ‌returning‌ ‌the‌ ‌current‌ ‌element‌ ‌count.‌

So,‌ ‌you‌ ‌can‌ ‌use‌ ‌‌ new‌ ‌Map()‌‌ to‌ ‌create‌ ‌a‌ ‌map‌ ‌like‌ ‌this:‌

let map = new Map();
map.set('10', 'str'); // string key
map.set(10, 'num'); // numeric key
map.set(false, 'bool'); //  boolean key
 
//Map can save the type, and a regular object can convert keys to a string,
//so these two values are different:
console.log(map.get(10)); // 'num'
console.log(map.get('10')); // 'str'
console.log(map.size); // 3

From‌ ‌this‌ ‌example,‌ ‌you‌ ‌can‌ ‌notice‌ ‌that‌ ‌they‌ ‌are‌ ‌not‌ ‌converted‌ ‌to‌ ‌strings,‌ ‌as‌ ‌any‌ ‌type‌ ‌of‌ ‌key‌ ‌is‌ ‌allowed.‌

Also,‌ ‌maps‌ ‌are‌ ‌capable‌ ‌of‌ ‌using‌ ‌objects‌ ‌as‌ ‌keys‌ ‌as‌ ‌demonstrated‌ ‌below:‌

let js = {
  name: "Javascript"
};
// for each book, let's save the number of visits
let visitsCountMap = new Map();
// js is the key for the map
visitsCountMap.set(js, 250);
console.log(visitsCountMap.get(js)); // 250

One‌ ‌of‌ ‌the‌ ‌most‌ ‌significant‌ ‌map‌ ‌features‌ ‌is‌ ‌using‌ ‌objects‌ ‌as‌ ‌keys:‌

let js = {
  name: "Javascript"
};
let visitsCountObj = {}; // try to use an object
visitsCountObj[js] = 250; // try to use js object as the key
console.log(visitsCountObj["[object Object]"]); // 250

In‌ ‌the‌ ‌example‌ ‌above,‌ ‌‌visitsCountObj‌‌ ‌is‌ ‌an‌ ‌object‌ ‌that‌ ‌converts‌ ‌all‌ ‌the‌ ‌keys‌ ‌like‌ ‌js‌‌ ‌to‌ ‌strings.‌ ‌So,‌ ‌the‌ ‌string‌ ‌key‌ ‌is‌ ‌‌ "[object‌ ‌Object]"‌.‌ ‌But,‌ ‌it‌ ‌is‌ ‌not‌ ‌completely‌ ‌ right.‌

The‌ ‌SameValueZer‌ ‌algorithm‌ ‌is‌ ‌used‌ ‌for‌ ‌testing‌ ‌keys‌ ‌for‌ ‌equivalence.‌ ‌It‌ ‌is‌ ‌ similar‌ ‌to‌ ‌‌===‌.‌ ‌The‌ ‌only‌ ‌difference‌ ‌is‌ ‌that‌ ‌‌NaN‌‌ ‌is‌ ‌equal‌ ‌to‌ ‌‌NaN‌‌ ‌and‌ ‌can‌ ‌be‌ ‌applied‌ ‌as‌ ‌the‌ ‌key.‌ ‌You‌ ‌can’t‌ ‌change‌ ‌or‌ ‌customize‌ ‌this‌ ‌algorithm.‌

There‌ ‌is‌ ‌another‌ ‌option,‌ ‌too:‌ ‌the‌ ‌calls‌ ‌can‌ ‌be‌ ‌chained,‌ ‌as‌ ‌each‌ ‌‌map.set‌‌ ‌‌call‌ ‌ returns‌ ‌the‌ ‌map,‌ ‌like‌ ‌this:‌

map.set('10', 'str')
  .set(10, 'num')
  .set(false, 'bool');

Iterating‌ ‌over‌ ‌Map‌ ‌

Three‌ ‌main‌ ‌methods‌ ‌exist‌ ‌that‌ ‌allow‌ ‌looping‌ ‌over‌ ‌a‌ ‌map.‌ ‌They‌ ‌are‌ ‌as‌ ‌follows:‌

  • map.keys()‌‌ ‌–‌ ‌returning‌ ‌an‌ ‌iterable‌ ‌for‌ ‌keys,‌
  • map.values()‌‌ ‌–‌ ‌returning‌ ‌an‌ ‌iterable‌ ‌for‌ ‌values,‌
  • map.entries()‌‌ ‌–‌ ‌returning‌ ‌an‌ ‌iterable‌ ‌for‌ ‌entries‌ ‌‌ ‌‌[key,‌ ‌value]‌,‌ ‌it’s‌ ‌applied‌ ‌by‌ ‌default‌ ‌in‌ ‌‌for..of‌.‌

An‌ ‌example‌ ‌is‌ ‌as‌ ‌follows:‌

let priceMap = new Map([
  ['banana', 250],
  ['apple', 150],
  ['peach', 200]
]);
// iterate over keys (fruit)
for (let fruit of priceMap.keys()) {
  console.log(fruit); // banana, apple, peach
}
// iterate over values (amounts)
for (let amount of priceMap.values()) {
  console.log(amount); // 250, 150, 200
}
// iterate over [key, value] entries
for (let entry of priceMap) { // the same as of priceMap.entries()
  console.log(entry); // banana, 250 (and so on)
}

Also,‌ ‌there‌ ‌exists‌ ‌a‌ ‌built-in‌ ‌‌ ‌‌forEach‌‌ ‌method,‌ ‌equivalent‌ ‌to‌ ‌‌ ‌‌Array‌:‌

let priceMap = new Map([
  ['banana', 250],
  ['apple', 150],
  ['peach', 200]
]);
// running the function for each pair (key, value)
priceMap.forEach((value, key, map) => {
  console.log(`${key}: ${value}`); // banana, 250 etc
});

Using‌ ‌Object.entries‌ ‌

Once‌ ‌a‌ ‌map‌ ‌is‌ ‌generated,‌ ‌an‌ ‌array‌ ‌can‌ ‌be‌ ‌passed‌ ‌with‌ ‌key/value‌ ‌pairs‌ ‌as‌ ‌ follows:‌

// array of pairs [key, value]
let map = new Map([
  ['10', 'str'],
  [10, 'num'],
  [false, 'bool']
]);
console.log(map.get('10')); // str

A‌ ‌map‌ ‌can‌ ‌be‌ ‌created‌ ‌from‌ ‌an‌ ‌object‌ ‌as‌ ‌follows:‌ ‌

let obj = {
  name: "Maria",
  age: 20
};
let map = new Map(Object.entries(obj));
console.log(map.get('name')); // Maria

Object.fromEntries‌ ‌

After‌ ‌learning‌ ‌how‌ ‌to‌ ‌create‌ ‌a‌ ‌map‌ ‌from‌ ‌a‌ ‌plain‌ ‌object‌ ‌using‌ ‌ Object.entries(obj)‌,‌ ‌let’s‌ ‌get‌ ‌to‌ ‌the‌ ‌opposite.‌

The‌ ‌‌ ‌‌Object.fromEntries‌‌method‌ ‌allows‌ ‌performing‌ ‌the‌ ‌reverse‌ ‌process.‌ ‌ Once‌ ‌you‌ ‌have‌ ‌an‌ ‌array‌ ‌of‌ ‌‌ [key,‌ ‌value]‌‌ ‌pairs,‌ ‌it‌ ‌will‌ ‌generate‌ ‌a‌ ‌object‌ ‌from‌ ‌them‌ ‌like‌ ‌this:‌

let counts = Object.fromEntries([
  ['banana', 4],
  ['apple', 2],
  ['peach', 3]
]);
// now counts = { banana: 4, apple: 2, peach: 3 }
console.log(counts.apple); // 2

So,‌ ‌‌‌‌Object.fromEntries‌‌ ‌can‌ ‌be‌ ‌used‌ ‌for‌ ‌getting‌ ‌a‌ ‌plain‌ ‌object‌ ‌from‌ ‌the‌ ‌map.‌ ‌ Once‌ ‌you‌ ‌store‌ ‌data‌ ‌inside‌ ‌a‌ ‌map,‌ ‌you‌ ‌should‌ ‌pass‌ ‌it‌ ‌to‌ ‌a‌ ‌third-party‌ ‌code,‌ ‌ expecting‌ ‌a‌ ‌plain‌ ‌object,‌ ‌as‌ ‌demonstrated‌ ‌below:‌

let map = new Map();
map.set('banana', 4);
map.set('apple', 2);
map.set('peach', 3);
 
let obj = Object.fromEntries(map.entries()); //(*)
// obj = { banana: 4, apple: 2, peach: 3 }
console.log(obj.apple); // 2

The‌ ‌‌(*)‌‌ line‌ ‌can‌ ‌be‌ ‌made‌ ‌shorter:‌

let obj = Object.fromEntries(map); // .entries()

Sets‌ ‌

A‌ ‌set‌ ‌is‌ ‌a‌ ‌specific‌ ‌collection‌ ‌of‌ ‌“set‌ ‌of‌ ‌values”‌ ‌where‌ ‌every‌ ‌value‌ ‌can‌ ‌take‌ ‌place‌ ‌only‌ ‌once.‌

The‌ ‌primary‌ ‌methods‌ ‌used‌ ‌by‌ ‌set‌ ‌are‌ ‌shown‌ ‌below:‌

  • new‌ ‌Set(iterable)‌‌ ‌–‌ ‌creating‌ ‌the‌ ‌set,‌ ‌and‌ ‌if‌ ‌an‌ ‌‌iterable‌‌ ‌object‌ ‌is‌ ‌supplied‌ ‌(usually‌ ‌an‌ ‌array),‌ ‌copying‌ ‌values‌ ‌from‌ ‌it‌ ‌to‌ ‌the‌ ‌set.‌
  • set.add(value)‌‌ ‌ ‌–‌ ‌adding‌ ‌a‌ ‌value,‌ ‌returning‌ ‌the‌ ‌set‌ ‌itself.‌
  • set.delete(value)‌‌ ‌ ‌‌–‌ ‌removing‌ ‌the‌ ‌value,‌ ‌returning‌ ‌‌true‌‌ ‌if‌ ‌the‌ ‌‌value‌‌ ‌is‌ ‌there‌ ‌at‌ ‌the‌ ‌moment‌ ‌of‌ ‌the‌ ‌calling,‌ ‌otherwise‌ ‌‌false‌.‌
  • set.has(value)‌‌ ‌ ‌–‌ ‌returning‌ ‌‌true‌‌ ‌if‌ ‌the‌ ‌value‌ ‌is‌ ‌there‌ ‌inside‌ ‌the‌ ‌set,‌ ‌otherwise‌ ‌‌false‌.‌
  • set.clear()‌‌ ‌ ‌–‌ ‌removing‌ ‌all‌ ‌from‌ ‌the‌ ‌set.‌
  • set.size‌‌ ‌ ‌–‌ ‌counting‌ ‌the‌ ‌elements.‌

Set‌ ‌can‌ ‌be‌ ‌useful‌ ‌in‌ ‌various‌ ‌situations.‌ ‌For‌ ‌example,‌ ‌visitors‌ ‌are‌ ‌coming‌ ‌to‌ ‌your‌ ‌site,‌ ‌and‌ ‌you‌ ‌wish‌ ‌to‌ ‌remember‌ ‌all‌ ‌of‌ ‌them.‌ ‌Take‌ ‌into‌ ‌account‌ ‌that‌ ‌repeated‌ ‌visits‌ ‌must‌ ‌not‌ ‌lead‌ ‌to‌ ‌duplicates,‌ ‌and‌ ‌each‌ ‌visitor‌ ‌should‌ ‌be‌ ‌counted‌ ‌once.‌ ‌It‌ ‌can‌ ‌be‌ ‌easily‌ ‌done‌ ‌with‌ ‌Set‌ ‌like‌ ‌this:‌

let set = new Set();
let js = {
  name: "Javascript"
};
let html = {
  name: "Html"
};
let css = {
  name: "Css"
};
// visits, some books come multiple times
set.add(js);
set.add(css);
set.add(html);
set.add(js);
set.add(css);
// set only stores unique values
console.log(set.size); // 3
for (let user of set) {
  console.log(user.name); // Javascripr (then Css and Html)
}

‌Iterating‌ ‌over‌ ‌Set‌ ‌

Looping‌ ‌over‌ ‌a‌ ‌set‌ ‌is‌ ‌possible‌ ‌either‌ ‌with‌ ‌‌ ‌‌for..of‌‌ ‌‌or‌ ‌with‌ ‌‌ ‌‌for..of‌‌ ‌like‌ ‌this:‌

let set = new Set(["bananas", "apples", "peaches"]);
for (let value of set) {
  console.log(value);
}
set.forEach((value, valueAgain, set) => {
  console.log(value);
});

Here‌ ‌is‌ ‌an‌ ‌interesting‌ ‌and‌ ‌a‌ ‌bit‌ ‌funny‌ ‌fact.‌ ‌The‌ ‌‌callback‌‌ ‌function‌ ‌that‌ ‌is‌ ‌passed‌ ‌inside‌ ‌‌ ‌‌forEach‌‌ ‌includes‌ ‌three‌ ‌arguments:‌ ‌a‌ ‌value,‌ ‌the‌ ‌same‌ ‌value‌ ‌‌ ‌‌valueAgain‌,‌ ‌ and‌ ‌the‌ ‌target‌ ‌object.‌ ‌As‌ ‌you‌ ‌can‌ ‌see,‌ ‌the‌ ‌same‌ ‌value‌ ‌can‌ ‌appear‌ ‌in‌ ‌the‌ ‌ arguments‌ ‌twice.‌

That‌ ‌can‌ ‌seem‌ ‌strange‌ ‌but‌ ‌helps‌ ‌in‌ ‌replacing‌ ‌the‌ ‌Map‌ ‌with‌ ‌Set‌ ‌in‌ ‌particular‌ ‌ cases.‌

‌Summary‌ ‌

In‌ ‌this‌ ‌chapter,‌ ‌we‌ ‌represented‌ ‌to‌ ‌you‌ ‌Map‌ ‌and‌ ‌Set.‌ ‌Now,‌ ‌let’s‌ ‌summarize‌ ‌what‌ ‌was‌ ‌covered‌ ‌in‌ ‌general.‌ While‌ ‌Map‌ ‌is‌ ‌a‌ ‌collection‌ ‌of‌ ‌keyed‌ ‌values,‌ ‌Set‌ ‌represents‌ ‌a‌ ‌collection‌ ‌of‌ ‌unique‌ ‌values.‌ ‌Each‌ ‌of‌ ‌them‌ ‌has‌ ‌its‌ ‌methods‌ ‌and‌ ‌properties.‌

‌ ‌ ‌

Both‌ ‌of‌ ‌these‌ ‌data‌ ‌structures‌ ‌add‌ ‌extra‌ ‌capabilities‌ ‌of‌ ‌JavaScript‌ ‌and‌ ‌can‌ ‌ simplify‌ ‌common‌ ‌tasks‌ ‌(for‌ ‌example,‌ ‌detecting‌ ‌the‌ ‌length‌ ‌of‌ ‌a‌ ‌key/value‌ ‌pair‌ ‌ collection‌ ‌or‌ ‌deleting‌ ‌duplicate‌ ‌items‌ ‌from‌ ‌a‌ ‌data‌ ‌set).‌

‌ ‌ ‌


Do you find this helpful?

Related articles