ES6 Collections: Using Map, Set, WeakMap, WeakSet

Share this article

ES6 Collections: Using Map, Set, WeakMap, WeakSet

This article examines four new ES6 collections and the benefits they provide.

Most major programming languages have several types of data collections. Python has lists, tuples, and dictionaries. Java has lists, sets, maps, queues. Ruby has hashes and arrays. JavaScript, up until now, had only arrays. Objects and arrays were the workhorses of JavaScript. ES6 introduces four new data structures that will add power and expressiveness to the language: Map, Set, WeakSet, and WeakMap.

Searching for the JavaScript HashMap

HashMaps, dictionaries, and hashes are several ways that various programming languages store key/value pairs, and these data structures are optimized for fast retrieval.

In ES5, JavaScript objects — which are just arbitrary collections of properties with keys and values — can simulate hashes, but there are several downsides to using objects as hashes.

Downside #1: Keys must be strings in ES5

JavaScript object property keys must be strings, which limits their ability to serve as a collection of key/value pairs of varying data types. You can, of course, coerce/stringify other data types into strings, but this adds extra work.

Downside #2: Objects are not inherently iterable

Objects weren’t designed to be used as collections, and as a result there’s no efficient way to determine how many properties an object has. (See, for example, Object.keys is slow). When you loop over an object’s properties, you also get its prototype properties. You could add the iterable property to all objects, but not all objects are meant to be used as collections. You could use the for … in loop and the hasOwnProperty() method, but this is just a workaround. When you loop over an object’s properties, the properties won’t necessarily be retrieved in the same order they were inserted.

Downside #3: Challenges with built-in method collisions

Objects have built-in methods like constructor, toString, and valueOf. If one of these was added as a property, it could cause collisions. You could use Object.create(null) to create a bare object (which doesn’t inherit from object.prototype), but, again, this is just a workaround.

ES6 includes new collection data types, so there’s no longer a need to use objects and live with their drawbacks.

Using ES6 Map Collections

Map is the first data structure/collection we’ll examine. Maps are collections of keys and values of any type. It’s easy to create new Maps, add/remove values, loop over keys/values and efficiently determine their size. Here are the crucial methods:

Creating a map and using common methods

const map = new Map(); // Create a new Map
map.set('hobby', 'cycling'); // Sets a key value pair

const foods = { dinner: 'Curry', lunch: 'Sandwich', breakfast: 'Eggs' }; // New Object
const normalfoods = {}; // New Object

map.set(normalfoods, foods); // Sets two objects as key value pair

for (const [key, value] of map) {
  console.log(`${key} = ${value}`); // hobby = cycling  [object Object] = [object Object]
}

map.forEach((value, key) => {
  console.log(`${key} = ${value}`);
}, map); // hobby = cycling  [object Object] = [object Object]

map.clear(); // Clears key value pairs
console.log(map.size === 0); // True

Run this example on JSBin

Using the Set Collection

Sets are ordered lists of values that contain no duplicates. Instead of being indexed like arrays are, sets are accessed using keys. Sets already exist in Java, Ruby, Python, and many other languages. One difference between ES6 Sets and those in other languages is that the order matters in ES6 (not so in many other languages). Here are the crucial Set methods:

const planetsOrderFromSun = new Set();
planetsOrderFromSun.add('Mercury');
planetsOrderFromSun.add('Venus').add('Earth').add('Mars'); // Chainable Method
console.log(planetsOrderFromSun.has('Earth')); // True

planetsOrderFromSun.delete('Mars');
console.log(planetsOrderFromSun.has('Mars')); // False

for (const x of planetsOrderFromSun) {
  console.log(x); // Same order in as out - Mercury Venus Earth
}
console.log(planetsOrderFromSun.size); // 3

planetsOrderFromSun.add('Venus'); // Trying to add a duplicate
console.log(planetsOrderFromSun.size); // Still 3, Did not add the duplicate

planetsOrderFromSun.clear();
console.log(planetsOrderFromSun.size); // 0

Run this example on JSBin

Weak Collections, Memory, and Garbage Collections

JavaScript Garbage Collection is a form of memory management whereby objects that are no longer referenced are automatically deleted and their resources are reclaimed.

Map and Set‘s references to objects are strongly held and will not allow for garbage collection. This can get expensive if maps/sets reference large objects that are no longer needed, such as DOM elements that have already been removed from the DOM.

To remedy this, ES6 also introduces two new weak collections called WeakMap and WeakSet. These ES6 collections are ‘weak’ because they allow for objects which are no longer needed to be cleared from memory.

WeakMap

WeakMap is the third of the new ES6 collections we’re covering. WeakMaps are similar to normal Maps, albeit with fewer methods and the aforementioned difference with regards to garbage collection.

const aboutAuthor = new WeakMap(); // Create New WeakMap
const currentAge = {}; // key must be an object
const currentCity = {}; // keys must be an object

aboutAuthor.set(currentAge, 30); // Set Key Values
aboutAuthor.set(currentCity, 'Denver'); // Key Values can be of different data types

console.log(aboutAuthor.has(currentCity)); // Test if WeakMap has a key

aboutAuthor.delete(currentAge); // Delete a key

Run this example on JSBin

Use cases

WeakMaps have several popular use cases. They can be used to keep an object’s private data private, and they can also be used to keep track of DOM nodes/objects.

Private data use case

The following example is from JavaScript expert Nicholas C. Zakas:

var Person = (function() {
  var privateData = new WeakMap();

  function Person(name) {
    privateData.set(this, { name: name });
  }

  Person.prototype.getName = function() {
    return privateData.get(this).name;
  };

  return Person;
}());

Using a WeakMap here simplifies the process of keeping an object’s data private. It’s possible to reference the Person object, but access to the privateDataWeakMap is disallowed without the specific Person instance.

DOM nodes use case

The Google Polymer project uses WeakMaps in a piece of code called PositionWalker.

PositionWalker keeps track of a position within a DOM subtree, as a current node and an offset within that node.

WeakMap is used to keep track of DOM node edits, removals, and changes:

_makeClone() {
  this._containerClone = this.container.cloneNode(true);
  this._cloneToNodes = new WeakMap();
  this._nodesToClones = new WeakMap();

  ...

  let n = this.container;
  let c = this._containerClone;

  // find the currentNode's clone
  while (n !== null) {
    if (n === this.currentNode) {
    this._currentNodeClone = c;
    }
    this._cloneToNodes.set(c, n);
    this._nodesToClones.set(n, c);

    n = iterator.nextNode();
    c = cloneIterator.nextNode();
  }
}

WeakSet

WeakSets are Set Collections whose elements can be garbage collected when objects they’re referencing are no longer needed. WeakSets don’t allow for iteration. Their use cases are rather limited (for now, at least). Most early adopters say that WeakSets can be used to tag objects without mutating them. ES6-Features.org has an example of adding and deleting elements from a WeakSet in order to keep track of whether or not the objects have been marked:

let isMarked     = new WeakSet()
let attachedData = new WeakMap()

export class Node {
    constructor (id)   { this.id = id                  }
    mark        ()     { isMarked.add(this)            }
    unmark      ()     { isMarked.delete(this)         }
    marked      ()     { return isMarked.has(this)     }
    set data    (data) { attachedData.set(this, data)  }
    get data    ()     { return attachedData.get(this) }
}

let foo = new Node("foo")

JSON.stringify(foo) === '{"id":"foo"}'
foo.mark()
foo.data = "bar"
foo.data === "bar"
JSON.stringify(foo) === '{"id":"foo"}'

isMarked.has(foo)     === true
attachedData.has(foo) === true
foo = null  /* remove only reference to foo */
attachedData.has(foo) === false
isMarked.has(foo)     === false

Map All Things? Records vs ES6 Collections

Maps and Sets are nifty new ES6 collections of key/value pairs. That said, JavaScript objects still can be used as collections in many situations. No need to switch to the new ES6 collections unless the situation calls for it.

MDN has has a nice list of questions to determine when to use an object or a keyed collection:

  • Are keys usually unknown until run time, and do you need to look them up dynamically?
  • Do all values have the same type, and can be used interchangeably?
  • Do you need keys that aren’t strings?
  • Are key-value pairs often added or removed?
  • Do you have an arbitrary (easily changing) amount of key-value pairs?
  • Is the collection iterated?

New ES6 Collections Yield a More Usable JavaScript

JavaScript collections have previously been quite limited, but this has been remedied with ES6. These new ES6 collections will add power and flexibility to the language, as well as simplify the task of JavaScript developers who adopt them.


This article is part of the web development series from Microsoft tech evangelists and DevelopIntelligence on practical JavaScript learning, open-source projects, and interoperability best practices including Microsoft Edge browser and the new EdgeHTML rendering engine. DevelopIntelligence offers JavaScript Training and React Training Courses through appendTo, their front-end focused blog and course site.

We encourage you to test across browsers and devices including Microsoft Edge — the default browser for Windows 10 — with free tools on dev.microsoftedge.com, including the EdgeHTML issue tracker, where you can report or search EdgeHTML issues, such as problems with site rendering or standards compliance. Also, visit the Edge blog to stay updated and informed from Microsoft developers and experts.

Frequently Asked Questions (FAQs) about ES6 Collections: Map, Set, WeakMap, WeakSet

What are the main differences between Map and WeakMap in JavaScript ES6?

In JavaScript ES6, both Map and WeakMap are used to store key-value pairs. However, there are some significant differences between them. Firstly, in a Map, keys can be of any type, whereas in a WeakMap, keys must be objects. Secondly, Map has a size property that allows you to check the number of key-value pairs, but WeakMap does not have this property. Lastly, Map holds strong references to key objects, which means they are not eligible for garbage collection as long as the Map exists. On the other hand, WeakMap holds weak references to key objects, which means they can be garbage collected if there are no other references to the object.

How can I iterate over a WeakMap or WeakSet in JavaScript ES6?

Unlike Map and Set, WeakMap and WeakSet do not have methods for iterating over their elements. This is because they are designed to hold weak references to their keys (WeakMap) or values (WeakSet), which means these can be garbage collected at any time. Therefore, there is no guarantee that an element will still exist when you try to iterate over it. If you need to iterate over a collection, you should use a Map or Set instead.

Can I use primitive data types as keys in a WeakMap or WeakSet?

No, you cannot use primitive data types as keys in a WeakMap or WeakSet. The keys in these collections must be objects. This is because WeakMap and WeakSet hold weak references to their keys, which means the keys can be garbage collected if there are no other references to them. Primitive data types, such as numbers and strings, are not garbage collected in the same way as objects, so they cannot be used as keys in these collections.

Why would I use a WeakMap or WeakSet instead of a Map or Set?

WeakMap and WeakSet have some unique features that can make them more suitable than Map or Set in certain situations. Because they hold weak references to their keys (WeakMap) or values (WeakSet), these can be garbage collected when they are no longer in use. This can be useful if you want to associate additional data with an object, but you don’t want to prevent the object from being garbage collected when it is no longer needed. Additionally, because WeakMap and WeakSet do not have methods for iterating over their elements, they can provide a level of privacy for the data they store.

What happens when a key in a WeakMap or WeakSet is garbage collected?

When a key in a WeakMap or WeakSet is garbage collected, the corresponding entry in the collection is automatically removed. This is because these collections hold weak references to their keys, which means the keys can be garbage collected when they are no longer in use. This feature can be useful for managing memory in your JavaScript applications, as it ensures that data associated with objects that are no longer in use is also cleaned up.

Can I use a WeakMap or WeakSet to store temporary data?

Yes, WeakMap and WeakSet can be ideal for storing temporary data. Because they hold weak references to their keys (WeakMap) or values (WeakSet), these can be garbage collected when they are no longer in use. This means that the data stored in these collections will also be cleaned up when the keys are garbage collected. This can be useful for storing data that is only needed for a short period of time, as you don’t have to worry about manually cleaning it up.

How can I check if a WeakMap or WeakSet contains a certain key or value?

You can use the has method to check if a WeakMap or WeakSet contains a certain key. This method returns a boolean indicating whether the key exists in the collection. However, remember that you cannot use this method to check for a certain value in a WeakSet, as the values in this collection are not accessible.

Can I remove an entry from a WeakMap or WeakSet?

Yes, you can remove an entry from a WeakMap using the delete method. This method removes the entry associated with the given key and returns a boolean indicating whether the key existed in the collection. However, you cannot remove an entry from a WeakSet, as this collection does not have a delete method.

Can I clear all entries from a WeakMap or WeakSet?

No, you cannot clear all entries from a WeakMap or WeakSet. These collections do not have a clear method, which is available in Map and Set. This is because WeakMap and WeakSet are designed to automatically clean up their entries when the keys are garbage collected.

Can I get the size of a WeakMap or WeakSet?

No, you cannot get the size of a WeakMap or WeakSet. These collections do not have a size property, which is available in Map and Set. This is because the size of a WeakMap or WeakSet can change at any time due to garbage collection.

Kyle PennellKyle Pennell
View Author

Kyle is a Technical Instructor at DevelopIntelligence. He spends his time reading, coding, biking, and exploring live music in Denver. He enjoys trying to make technical concepts more approachable and likes tinkering with music and mapping APIs.

es6learn-modernjsmapmdnmodernjsmodernjs-hubSetWeakMapWeakSet
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week