A Step-by-step Introduction to JavaScript Sets

As mentioned previously, we have a new JavaScript standard commonly known as ECMAScript 6 (ES6). I've spent quite a bit of time recently reading around the new features outlined in the standard that are coming to, or have been recently implemented in, our browsers. One of my favorite additions is the new Set type. A set is somewhat like an array. It's a place to store values: numbers, strings, objects, actual arrays, Booleans, other sets etc. The most notable difference is that it will only store the same element once.

Creating and Adding Elements to a Set

It's really easy to set-up a new, empty, set:

var A = new Set(); //A is a set with nothing in it

Then we can add elements to the set using the chainable add method:

A.add(1).add(2).add("1").add([1, 2, 3, 4, 5]).add({}); //The set now contains 5 elements of various types

Note that 1 and "1" are different — there is no type coercion so both can be added to the set. On the other hand, if we try to add the number 1 again then nothing changes:

A.add(1); //The set still contains 5 elements

However, we could add another empty object to the array.

A.add({}); //The set contains 6 elements, 2 of them are empty objects

Why does this work? Because two objects are only equal if they refer to the same space in memory. The two objects may contain the same properties and methods but they are not considered equal here. The same holds for arrays:

A.add([1, 2, 3, 4, 5]); //The set contains 7 elements, including two arrays

Conversely, the following only adds one array to the set, not two or three because x and y refer to the same object in memory.

var x = ['a', 'b', 'c']; var y = x; //y is an alias of x A.add(x).add(x).add(y); //Only the first call to add adds anything to the set

You can also add elements to a set by passing in any iterable as an argument to the original construction call. The simplest option is an array:

var B = new Set([7, 8, 9]); //B is a set containing 3 elements

Confusingly, strings are iterable (but numbers are not). So this results in a TypeError...

B = new Set(123); //Nope, can't do this

...but the following creates a set with three elements!

B = new Set('123'); //B is a set containing three elements, the strings '1', '2' and '3'

To add a single string to a new set, place it in an array:

B = new Set(['123']); //B is a set containing the single element (string) '123'

There's nothing to stop you making sets of sets using the add method:

B.add(new Set([1, 2, 3])) //B now contains 2 elements, the string '123' and the set of numbers 1, 2 and 3 B.add(new Set([7, 8, 9])) //B now contains 3 elements, including 2 sets

On the other hand, the following just creates a set containing the first three positive integers:

var C = new Set(new Set([1, 2, 3])); //Same as var C = new Set([1,2,3])

You can also clone (rather than alias) another set using new Set:

var D = new Set(A); //A and D are completely different sets, they just (currently) have the same members

Checking for Set Membership

Checking for set membership is also really easy using the has method.

A.has(1); //true A.has(2); //true A.has('1'); //true A.has(75); //false

Of course you can't just check for an empty object or the array [1, 2, 3, 4, 5] for the same reason that you can add more than one such object or array.

A.has({}); //false A.has([1, 2, 3, 4, 5]); //false

You can, however, check for specific objects or arrays (or dates or other non-primitives) that you have references to.

A.has(x); //true A.has(y); //true: y is an alias for x (see above)

Removing Elements from a Set

The delete method removes members from a set.

A.delete(1); //Removes 1 from the set, returns true A.has(1); //false A.delete(x); //true A.has(y); //false: y is an alias for the array x which is no longer a member of A

As noted earlier, the add method is chainable because it returns this. By contrast, if you use pop or shift on a regular array they return the extracted element. The delete method of Setdoes neither of these things, it just returns a Boolean to indicate whether or not an element was deleted. Hence you can't chain delete calls. And of course you can't delete an object or array you don't have a reference to:

A.delete({}); //false A.delete([1, 2, 3, 4, 5]); //false You can remove all elements from a set in one step using the clear method (which returns undefined regardless of whether anything was cleared or not):B.clear(); //B is now an empty set.

Checking the Size of a Set

Like an array, you can check the number of elements contained with a Set. Unlike an array, the relevant property is called size, not length.

A.size; //6: The number 2, the string '1', 2 arrays containing the numbers 1 to 5 and 2 empty objects B.size; //0

Looping Over a Set

A set doesn't allow for any form of random access. For instance, you can't access the first or any other element of the set using square bracket notation like you can with an array. (Having said that, if you try the result is undefined, not an error.) This also means you can't use an old fashioned for loop. for-in loops don't work either. However, there are a couple of options for getting at elements. You can use the new JavaScript for-of loop for looping over any iterable object, and that includes sets. The following simply logs all elements to the console in the order they were added to the set:

for(var element in A) { console.log(element); //Prints out representations of the 6 elements }

And, like arrays, sets have a forEach method. This does the same as the example above:

A.forEach(function(el){console.log(el);}); //Does the same as the for-of loop above

Uses and Limitations

I like JavaScript sets because they seem relatively easy to use. They provide a simple system for holding and accessing a collection of data when you don't want to worry about duplicates and the problems they can cause. Unsurprisingly, they also somewhat resemble the mathematical concept of a set. Because of this it's perhaps slightly surprising that the ES6 specification doesn't include methods for performing common mathematical set operations such as union, intersection and symmetric difference. For the time being at least, you have to implement these for yourself (some pointers can be found here). Moreover, there's no direct way of representing, say, the set of all natural numbers or the set of all real numbers in a JavaScript set (since they're both infinite sets) as you might wish to in mathematics.

Browser Support

Support for sets is already good in desktop browsers. Mobile browsers are still catching up.

Further Reading

I found the online book Exploring ES6 by Dr Axel Rauschmayer a great reference for all things ES6. Chapter 19 covers Sets and the related "collections" WeakSets, Maps and WeakMaps.This blog post on Collections by Jason Orendorff for Mozilla is also well worth a read

 

Try our jQuery HTML5 controls for your web apps and take immediate advantage of their stunning data visualization capabilities. Download Free Trial today.


Add a Comment

Be the first one to add a comment. Please Login or Register to add comment.