loading...
Cover image for Sets in Javascript ES6

Sets in Javascript ES6

damcosset profile image Damien Cosset ・3 min read

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.

Posted on by:

damcosset profile

Damien Cosset

@damcosset

French web developer mostly interested in Javascript and JAVA

Discussion

markdown guide
 

Thanks for the post Damien. You should do a follow up on Maps, WeakMaps. Maybe go into explaining how Weakmaps 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 with set.has(obj), I would expect the set to return false even if it still contains {} (the object), as the test is equal to set.has(null) and you never added null to the set. Or am I wrong?

 

In that last example, we added a reference to the object, inside the variable obj. By setting obj 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! πŸ˜‹

 

So, if there are no other references to an object, the reference inside the set will disappear.

I never took the time to actually learn what "weak" things were. Thanks :D