Introduction
Before ES6, Javascript had no native implementation of Sets. What is a set? A set is a list of values that cannot contain duplicates. Let's explore what problems that native ES6 Set solves, and how we can use them.
Constructor
To create a new set, we can use new Set()
. You can also give an iterator directly in the constructor.
const set = new Set()
console.log(set) // Set {}
const set = new Set([1, 2, 3])
console.log(set) // Set { 1, 2, 3 }
If you give an iterable with duplicates, the Set will ignore the duplicates after the first one:
const set = new Set([1, 2, 2, 2, 3, 4, 5, 5, 5, 4])
// Set { 1, 2, 3, 4, 5 }
The add method and size property
Sets have an add method that allow you to add a single item to the set. Sets also have a size property to retrieve the number of items in the set.
const set = new Set()
set.size // 0
set.add(2)
set.size // 1
add is ignored if the set already has the value:
const set = new Set(['Hello', 'World'])
set.add('Hello')
console.log(set) // Set { 'Hello', 'World' }
Problems solved from workarounds
Before sets, you would have to use normal objects to simulate a set. Because only strings can be use as keys, some problems could arise. 5 would be coerced into "5", {} would be "[object Object]". Sets do not coerce values. 5 and "5" are two different values.
const set = new Set()
set.add({})
set.add({})
set.size // 2
console.log(set) // Set { {}, {} }
set.add(5)
set.add('5')
set.add(5) // this will be ignored
set.size // 4
console.log(set) // Set { {}, {}, 5, '5' }
Therefore, multiple objects can be added to the set. Sets use Object.is()
to compare two values:
Object.is(5, 5) //true
Object.is(5, '5') //false
Object.is({},{}) //false
The has, delete and clear methods
- has(value) checks if the value is in the set. Returns true or false
- delete(value) removes the value from the set
- clear() removes all values from the set
const set = new Set()
set.add(5)
set.has(5) // true
set.has(8) // false
const set = new Set([1, 2, 3])
set.size // 3
set.delete(2)
set.size // 2
set.clear()
set.size // 0
Iteration
To iterate through a set, you can use the forEach() method. There is one little difference from when you are using it on an object/array. forEach() takes three arguments:
- the value
- the key ( index )
- the array or object your are iterating
However, in a set, the first two arguments are the same. That is because sets don't have keys. So:
const set = new Set([1, 2, 3])
set.forEach((value, key, s) => {
console.log(`${value} ${key}`)
console.log( set === s)
})
//1 1
//true
//2 2
//true
//3 3
//true
That way, you can use the same method that you already used for arrays and objects.
Set => Array
We already saw that you can convert an array to a set by passing an array to the Set constructor. You can also convert a set to an array by using the spread operator:
const set = new Set([1, 2, 3])
const array = [...set]
console.log(array) // [ 1, 2, 3 ]
Weak sets
These sets could be called strong sets, because it holds object references. It works as if you would store an object inside a variable. As long as the set instance exists, the object can't be garbage-collected in order to free memory.
const set = new Set()
let obj = {}
set.add(obj)
set.size // 1
obj = null
set.size // 1
// We can retrieve the original reference
obj = [...set][0]
console.log(obj) // {}
In some cases, you do want references in a set to disappear if all the other references disappear. ES6 includes weak sets. Weak sets can only store weak object references. So, if there are no other references to an object, the reference inside the set will disappear. WeakSet also cannot contain primitive values ( no strings or integers )
const set = new WeakSet()
let obj = {}
set.add(5) // ERROR
let obj = {}
set.add(obj)
set.has(obj) // true
obj = null // remove the reference to obj, also removes it in set
set.has(obj) // false
WeakSets:
- will throw errors if you pass nonobjects to add(), has() or delete().
- are not iterables. You can't use for-of or the forEach() method.
- do not have a size property.
The limited functionality of weak sets is necessary to properly handle memory.
Conclusion
ES6 gives you a new way to create sets and solves a lot of ES5 issues with the workarounds developers used. Use weak sets if you only need to track object references and prevent memory leaks.
Top comments (6)
Thanks for the post Damien. You should do a follow up on
Maps
,WeakMaps
. Maybe go into explaining howWeakmaps
are great for meta data.Thank for the comment. I'm working on it ;)
Thanks for the post. The last example is a little confusing to me: When setting
obj = null
, and afterwards testing withset.has(obj)
, I would expect the set to return false even if it still contains{}
(the object), as the test is equal toset.has(null)
and you never addednull
to the set. Or am I wrong?In that last example, we added a reference to the object, inside the variable
obj
. By settingobj
to null, we eliminate the last reference to the object we stored in our set. Therefore, because this is a WeakSet, the object is garbage-collected in order to free memory.Hope it's clear :)
Probably the cleanest cover image you've had Damian! 😋
I never took the time to actually learn what "weak" things were. Thanks :D