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
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
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
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.