Like I did in previous articles on the series, I would love to point out that you should probably set up Babel and follow along the examples with either a REPL or the
babel-node
CLI and a file. That’ll make it so much easier for you to internalize the concepts discussed in the series. If you aren’t the “install things on my computer” kind of human, you might prefer to hop on CodePen and then click on the gear icon for JavaScript – they have a Babel preprocessor which makes trying out ES6 a breeze. Another alternative that’s also quite useful is to use Babel’s online REPL – it’ll show you compiled ES5 code to the right of your ES6 code for quick comparison.
Before getting into it, let me shamelessly ask for your support if you’re enjoying my ES6 in Depth series. Your contributions will go towards helping me keep up with the schedule, server bills, keeping me fed, and maintaining Pony Foo as a veritable source of JavaScript goodies.
Thanks for reading that, and let’s go into collections now! For a bit of context, you may want to check out the article on iterators – which are closely related to ES6 collections – and the one on spread and rest parameters.
Now, let’s pick up where we left off – it’s time for
WeakMap
.
ES6 WeakMaps
You can think of WeakMap
as a subset of Map
. There are a few limitations on WeakMap
that we didn’t find in Map
. The biggest limitation is that WeakMap
is not iterable, as opposed to Map
– that means there is no iterable protocol, no .entries()
, no .keys()
, no .values()
, no .forEach()
and no .clear()
.
Another “limitation” found in WeakMap
as opposed to Map
is that every key
must be an object, and value types are not admitted as keys. Note that Symbol
is a value type as well, and they’re not allowed either.
var map = new WeakMap()
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
This is more of a feature than an issue, though, as it enables map keys to be garbage collected when they’re only being referenced as
WeakMap
keys. Usually you want this behavior when storing metadata related to something like a DOM node, and now you can keep that metadata in aWeakMap
. If you want all of those you could always use a regularMap
as we explored earlier.
You are still able to pass an iterable to populate a WeakMap
through its constructor.
var map = new WeakMap([[new Date(), 'foo'], [() => 'bar', 'baz']])
Just like with Map
, you can use .has
, .get
, and .delete
too.
var date = new Date()
var map = new WeakMap([[date, 'foo'], [() => 'bar', 'baz']])
console.log(map.has(date))
// <- true
console.log(map.get(date))
// <- 'foo'
map.delete(date)
console.log(map.has(date))
// <- false
Is This a Strictly Worse Map?
I know! You must be wondering – why the hell would I use WeakMap
when it has so many limitations when compared to Map
?
The difference that may make WeakMap
worth it is in its name. WeakMap
holds references to its keys weakly, meaning that if there are no other references to one of its keys, the object is subject to garbage collection.
Use cases for WeakMap
generally revolve around the need to specify metadata or extend an object while still being able to garbage collect it if nobody else cares about it. A perfect example might be the underlying implementation for process.on('unhandledRejection')
which uses a WeakMap
to keep track of promises that were rejected but no error handlers dealt with the rejection within a tick.
Keeping data about DOM elements that should be released from memory when they’re no longer of interest is another very important use case, and in this regard using WeakMap
is probably an even better solution to the DOM-related API caching solution we wrote about earlier using Map
.
In so many words then, no. WeakMap
is not strictly worse than Map
– they just cater to different use cases.
ES6 Sets
Sets are yet another collection type in ES6. Sets are very similar to Map
. To wit:
Set
is also iterableSet
constructor also accepts an iterableSet
also has a.size
property- Keys can also be arbitrary values
- Keys must be unique
NaN
equalsNaN
when it comes toSet
too- All of
.keys
,.values
,.entries
,.forEach
,,.get
,.set
.has
,.delete
, and.clear
However, there’s a few differences as well!
- Sets only have
values
- No
set.get
– but why would you wantget(value) => value
? - Having
set.set
would be weird, so we haveset.add
instead set[Symbol.iterator] !== set.entries
set[Symbol.iterator] === set.values
set.keys === set.values
set.entries()
returns an iterator on a sequence of items like[value, value]
In the example below you can note how it takes an iterable with duplicate values, it can be spread over an Array
using the spread operator, and how the duplicate value has been ignored.
var set = new Set([1, 2, 3, 4, 4])
console.log([...set])
// <- [1, 2, 3, 4]
Sets may be a great alternative to work with DOM elements. The following piece of code creates a Set
with all the <div>
elements on a page and then prints how many it found. Then, we query the DOM again and call set.add
again for every DOM element. Since they’re all already in the set
, the .size
property won’t change, meaning the set
remains the same.
function divs () {
return [...document.querySelectorAll('div')]
}
var set = new Set(divs())
console.log(set.size)
// <- 56
divs().forEach(div => set.add(div))
console.log(set.size)
// <- 56
// <- look at that, no duplicates!
ES6 WeakSets
Much like with WeakMap
and Map
, WeakSet
is Set
plus weakness minus the iterability – I just made that term up, didn’t I?
That means you can’t iterate over WeakSet
. Its values must be unique object references. If nothing else is referencing a value
found in a WeakSet
, it’ll be subject to garbage collection.
Much like in WeakMap
, you can only .add
, .has
, and .delete
values from a WeakSet
. And just like in Set
, there’s no .get
.
var set = new WeakSet()
set.add({})
set.add(new Date())
As we know, we can’t use primitive values.
var set = new WeakSet()
set.add(Symbol())
// TypeError: invalid value used in weak set
Just like with WeakMap
, passing iterators to the constructor is still allowed even though a WeakSet
instance is not iterable itself.
var set = new WeakSet([new Date(), {}, () => {}, [1]])
Use cases for WeakSet
vary, and here’s one from a thread on es-discuss – the mailing list for the ECMAScript-262 specification of JavaScript.
const foos = new WeakSet()
class Foo {
constructor() {
foos.add(this)
}
method () {
if (!foos.has(this)) {
throw new TypeError('Foo.prototype.method called on incompatible object!')
}
}
}
As a general rule of thumb, you can also try and figure out whether a WeakSet
will do when you’re considering to use a WeakMap
as some use cases may overlap. Particularly, if all you need to check for is whether a reference value is in the WeakSet
or not.
Next week we’ll be having
Proxy
for brunch :)
Comments