JavaScript
Article

Preparing for ECMAScript 6: Set and WeakSet

By Aurelio De Rosa

In one of my recent articles titled Preparing for ECMAScript 6: Map and WeakMap, I introduced you to two new data types available in ECMAScript 6: Map and its weak counterparts WeakMap. In this tutorial we’re going to cover another duo of similar data types called Set and WeakSet. They share a lot of similarities with Map and WeakMap, especially when it comes to the methods available. However, as we’ll discuss here, they have different scopes.

As I’ve pointed out in all the previous articles discussing ECMAScript 6, if you want to polyfill what we’ll cover, you can employ es6-shim by Paul Miller.

Set

Like the name says, the Set data type represents a set of elements (a collection). As mathematical notion suggests, this means that a set lets you store the same elements only once (e.g. the string “test” can’t be stored twice). Like other JavaScript data types, it isn’t mandatory to store elements of the same type, so in the same set you can store arrays, numbers, strings, and so on.

It’s also worth noting that a single element in a set cannot be retrieved, for example using a get() method. The reason is that an element has neither a key nor an index you can refer to in order to retrieve it. But because you can verify that an element is contained in a given Set instance, you don’t need a get() method. For example, if you know the string “test” is contained in a set you don’t need to retrieve it, because you already have that value. It’s still possible to retrieve all the elements stored, as you’ll learn in this tutorial.

“But when is this data type a good fit?” you may ask. Well, let’s say that you need to store the IDs of some elements. When it comes to these situations, you don’t want duplicates. Under these circumstances and in ECMAScript 5, most of you have probably used arrays or objects to store the elements. The problem is that every time a new element comes in, you have to check that it hasn’t been already added to avoid duplicates. If you used an array, you’d have code like this:

var collection = [1, 2, 3, 4, 5];
var newElements = [4, 8, 10];

for(var i = 0; i < newElements.length; i++) {
   if (collection.indexOf(newElements[i]) === -1) {
      collection.push(newElements[i]);
   }
}

Using the Set data type, you can simplify the previous code as shown below:

var collection = new Set([1, 2, 3, 4, 5]);
var newElements = [4, 8, 10];

for(var i = 0; i < newElements.length; i++) {
   collection.add(newElements[i]);
}

Now that you know what Set is and when to use it, let’s discuss the properties and the methods exposed.

Set.prototype.size

The size property returns the number of elements in a Set instance. This is similar to the length of the Array data type.

Set.prototype.constructor()

The constructor, as you might know, is used to instantiate new objects. It accepts an optional argument called iterable that is an array or an iterable object whose elements will be added to the new set. A basic example of use is shown below:

var array = [1, 2, "test", {a: 10}];
var set = new Set(array);

Set.prototype.add()

The add() method adds a new element to the set if it isn’t already present; otherwise the element isn’t added. The signature of this method is the following:

Set.prototype.add(value)

where value is the element you want to store. This method modifies the set it’s called upon but also returns the new set, allowing for chaining. An example of how to use such feature is shown below:

var set = new Set();
set.add("test").add(1).add({});

This method is currently implemented in Firefox, Internet Explorer 11, Chrome 38 and Opera 25. In versions of Chrome prior to 38 and Opera prior to 25 this method is supported behind the activation of the flag “Enable Experimental JavaScript”.

Set.prototype.delete()

In the same way we can add elements, we can also delete them from a set. To do that we can use the delete() method. It accepts the value to delete and returns true if the element is successfully removed or false otherwise. The signature of this method is shown below:

Set.prototype.delete(value)

value represents the element you want to delete.

This method is currently implemented in Firefox, Internet Explorer 11, Chrome 38 and Opera 25. In versions of Chrome prior to 38 and Opera prior to 25 you have to activate the usual flag.

Set.prototype.has()

The has() method is one of the methods that the Set data type has in common with Map. It allows us to verify if an element exists or not in the set. It returns true if the value is found or false otherwise. The signature of this method is as follows:

Set.prototype.has(value)

where value is the value you want to search for.

This method is currently implemented in Firefox, Internet Explorer 11, Chrome 38 and Opera 25. In versions of Chrome prior to 38 and Opera prior to 25 this method is supported behind the activation of the flag “Enable Experimental JavaScript”.

Set.prototype.clear()

The clear() method, like the one defined on Map, is a convenient way to remove all the elements from a Set instance. The method doesn’t have a return value (which means it returns undefined). The signature of clear() is shown below:

Set.prototype.clear()

clear() is currently implemented in Firefox, Internet Explorer 11, Chrome 38 and Opera 25. In versions of Chrome prior to 38 and Opera prior to 25 you have to activate the usual flag.

Set.prototype.forEach()

Another method in common with Map is forEach(). We can use it to iterate over the elements stored in the set in insertion order. The signature of forEach() is the following:

Set.prototype.forEach(callback[, thisArg])

callback is a function to run on each of the elements in the set. The thisArg parameter is used to set the context (this) of the callback. callback receives three parameters:

  • value: the value of the element processed
  • value: the value of the element processed
  • set: the Set object processed

As you can see, the value being processed is passed twice. The reason is to keep the method consistent with the forEach() implemented in Map and Array.

This method is supported by Firefox, Internet Explorer 11, Chrome 38 and Opera 25. In versions of Chrome prior to 38 and Opera prior to 25 you have to activate the usual flag.

Set.prototype.entries()

The entries() method enables us to obtain an Iterator to loop though the set’s elements. The Iterator contains an array of valuevalue pairs for each element in the set, in insertion order. The reason for this duplication is the same as before: to keep it consistent with the method of Map. The signature of this method is:

Set.prototype.entries()

This method is currently supported by Firefox, Chrome 38 and Opera 25. In versions of Chrome prior to 38 and Opera prior to 25 you have to activate the usual flag.

Set.prototype.values()

Another method that belongs to this data type is values(). It returns an Iterator object containing the values of the elements of the set, in insertion order. Its signature is the following:

Set.prototype.values()

This method is currently supported by Firefox, Chrome 38 and Opera 25. In versions of Chrome prior to 38 and Opera prior to 25 this method is supported behind the activation of the flag “Enable Experimental JavaScript”.

Set.prototype.keys()

Curiously enough, Set has also a keys() method. It performs the same operation as values(), so I won’t describe it.

WeakSet

WeakSet is the weak counterpart to the Set data type. A WeakSet only accepts objects as its values. This means that {}, function(){} (functions inherit from Object), and instances of your own classes are allowed, but "test", 1, and other primitive data types are not.

The other important difference is that WeakSet objects don’t prevent garbage collection if there aren’t any other references to an object stored (the reference is weak). Due to this difference, there aren’t any methods to retrieve values or more than one element at once such as Set.prototype.values() and Set.prototype.entries(). In addition, similarly to WeakMap, there isn’t a size property available.

As a final note, I want to highlight that Chrome 37 and Opera 24 support WeakSet and its methods without a flag, while the same isn’t true for Set. The newer version Chrome 38 and Opera 25 support Set and its methods by default.

Putting it all together

Now that you’ve seen all the methods and properties of the Set and the WeakSet data types, it’s time to put them into action. In this section I’ve developed two demos so that you can play with these methods and have a better idea of their power. As you’ll note, I haven’t used the Set.prototype.keys() method because I think it’s only good at confusing developers.

In the first demo I’ll use a Set object and its methods except Set.prototype.keys().

// Creates a new Set object
var set = new Set();
// Defines an array will be stored in the set
var arr = [4, 1, 9];

// Adds a new Number to the set
set.add(1);
// Adds a new String to the set
set.add('Aurelio De Rosa');
// Adds a new Object to the set
set.add({name: 'John Doe'});
// Adds a new Array element to the set
set.add(arr);

// Checks whether the string "test" is stored in the set. Prints "false"
console.log(set.has('test'));

// Checks whether the number "1" is stored in the set. Prints "true"
console.log(set.has(1));

// Retrieves the set size. Prints "4"
console.log(set.size);

// Deletes the object {name: 'Aurelio De Rosa'}. Prints "false" because even if it has the same values and properties, it's a different object
console.log(set.delete({name: 'Aurelio De Rosa'}));

// Retrieves the set size. Prints "4"
console.log(set.size);

// Deletes the array arr. Prints "true" because it's the same array
console.log(set.delete(arr));

// Retrieves the set size. Prints "3"
console.log(set.size);

// Loops over each element of the set
set.forEach(function(value, samevalue, set) {
    // Prints the value twice
    console.log('Value ' + value + ' is the same as ' + samevalue);
});

var entries = set.entries();
var entry = entries.next();
// Loops over each element of the set
while(!entry.done) {
    // Prints both the value and the key
    console.log('Value ' + entry.value[1] + ' is the same as ' + entry.value[0]);
    entry = entries.next();
}

var values = set.values();
var value = values.next();
// Loops over each value of the set
while(!value.done) {
    // Prints the value
    console.log('Value: ' + value.value);
    value = values.next();
}

// Deletes all the elements in the set
set.clear();

// Retrieves the set size. Prints "0"
console.log(set.size);

A live demo of the previous code is shown below and also available as a JSFiddle.

In this second demo we’ll see how we can work with a WeakSet object.

// Creates a new WeakSet object
var weakset = new WeakSet();
// Defines an object that will be stored in the set
var obj = {name: 'Aurelio De Rosa'};

// Adds an object to the set
weakset.add(obj);
// Adds a function to the set
weakset.add(function(){});
// Adds another object to the set
weakset.add({name: 'John Doe'});

// Checks whether the Object {name: 'John Doe'} exists in the weak set. Prints "false" because despite the fact that the passed object and the stored one have the same values and properties, they are different objects
console.log(weakset.has({name: 'John Doe'}));

// Checks whether the Object obj exists in the weak set. Prints "true" because it's the same object
console.log(weakset.has(obj));

// Deletes the obj element. Prints "true"
console.log(weakset.delete(obj));

// Deletes the function(){} element. Prints "false" because the passed function and the stored one they are different functions (objects)
console.log(weakset.delete(function(){}));

// Deletes all the elements of the weak set
weakset.clear();

A live demo of the previous code is shown below and also available as a JSFiddle.

Conclusion

In this tutorial I covered the new Set and WeakSet data types. In addition to Map and WeakMap they are the most interesting new types available in ECMAScript 6. I hope you enjoyed the article and learned something interesting.

Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.