ECMAScript 6 collections, Part 1: Sets
For most of JavaScript’s history, there has been only one type of collection represented by the Array type. Arrays are used in JavaScript just like arrays and other languages but pull double and triple duty mimicking queues and stacks as well. Since arrays only use numeric indices, developers had to use objects whenever a non-numeric index was necessary. ECMAScript 6 introduces several new types of collections to allow better and more efficient storing of order data.
Sets
Sets are nothing new if you come from languages such as Java, Ruby, or Python but have been missing from JavaScript. A set is in an ordered list of values that cannot contain duplicates. You typically don’t access items in the set like you would items in an array, instead it’s much more common to check the set to see if a value is present.
ECMAScript 6 introduces the Set type[1] as a set implementation for JavaScript. You can add values to a set by using the add() method and see how many items are in the set using size():
var items = new Set();
items.add(5);
items.add("5");
console.log(items.size()); // 2
ECMAScript 6 sets do not coerce values in determining whether or not to values are the same. So, a set can contain both the number 5 and the string "5" (internally, the comparison is done using ===). If the add() method is called more than once with the same value, all calls after the first one are effectively ignored:
var items = new Set();
items.add(5);
items.add("5");
items.add(5); // oops, duplicate - this is ignored
console.log(items.size()); // 2
You can initialize the set using an array, and the Set constructor will ensure that only unique values are used:
var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log(items.size()); // 5
In this example, an array with feed items is used to initialize the set. The number 5 Only appears once in the set even though it appears four times in the array. This functionality makes it easy to convert existing code or JSON structures to use sets.
You can test to see which items are in the set using the has() method:
var items = new Set();
items.add(5);
items.add("5");
console.log(items.has(5)); // true
console.log(items.has(6)); // false
Last, you can Remove an item from the set by using the delete() method:
var items = new Set();
items.add(5);
items.add("5");
console.log(items.has(5)); // true
items.delete(5)
console.log(items.has(5)); // false
All of this amounts to a very easy mechanism for tracking unique unordered values.
Iteration
Even though there is no random access to items in a set, it still possible to iterate over all of the sets values by using the new ECMAScript 6 for-of statement[2]. The for-of statement is a loop that iterates over values of a collection, including arrays and array-like structures. you can output values in a set like this:
var items = new Set([1, 2, 3, 4, 5]);
for (let num of items) {
console.log(num);
}
This code outputs each item in the set to the console in the order in which they were added to the set.
Example
Currently, if you want to keep track of unique values, the most common approach is to use an object and assign the unique values as properties with some truthy value. For example, there is a CSS Lint[3] rule that looks for duplicate properties. Right now, an object is used to keep track of CSS properties such as this:
var properties = {
"width": 1,
"height": 1
};
if (properties[someName]) {
// do something
}
Using an object for this purpose means always assigning a truthy value to a property so that the if statement works correctly (the other option is to use the in operator, but developers rarely do). This whole process can be made easier by using a set:
var properties = new Set();
properties.add("width");
properties.add("height");
if (properties.has(someName)) {
// do something
}
Since it only matters if the property was used before and not how many times it was used (there is no extra metadata associated), it actually makes more sense to use a set.
Another downside of using object properties for this type of operation is that property names are always converted to strings. So you can’t have an object with the property name of 5, you can only have one with the property name of "5". That also means you can’t easily keep track of objects in the same manner because the objects get converted to strings when assigned as a property name. Sets, on the other hand, can contain any type of data without fear of conversion into another type.
Browser Support
Both Firefox and Chrome have implemented Set, however, in Chrome you need to manually enable ECMAScript 6 features: go to chrome://flags and enable “Experimental JavaScript Features”. Both implementations are incomplete. Neither browser implements set iteration using for-of and Chrome’s implementation is missing the size() method.
Summary
ECMAScript 6 sets are a welcome addition to the language. They allow you to easily create a collection of unique values without worrying about type coercion. You can add and remove items very easily from a set even though there is no direct access to items in the set. It’s still possible, if necessary, to iterate over items in the set by using the ECMAScript 6 for-of statement.
Since ECMAScript 6 is not yet complete, it’s also possible that the implementation and specification might change before other browsers start to include Set. At this point in time, it is still considered experimental API and shouldn’t be used in production code. This post, and other posts about ECMAScript 6, are only intended to be a preview of functionality that is to come.
References
- Simple Maps and Sets (ES6 Wiki)
- for…of (MDN)
- CSS Lint
- Set (MDN)
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




11 Comments
Cool! I’d been hoping for more data structures.
One point: In most languages Sets are unordered. That’s certainly how they are in Java. See: http://en.wikipedia.org/wiki/Set_(computer_science)
Strange that they are ordered in ECMAScript. Any idea why?
Adam on September 25th, 2012 at 8:38 am
@Adam – This may just be an implementation detail, but the suggested implementation on the strawman shows that values are added to an array in the order in which they are added to the set. I’m not sure it makes all that much difference from a developer perspective, except that the ability to iterate over a set using
for-ofnow has a defined behavior.Nicholas C. Zakas on September 25th, 2012 at 9:43 am
Neat, but it seems a little under-powered without set operations like union, intersection, and difference.
Gabe Moothart on September 25th, 2012 at 10:14 am
Does anyone know a good reason why TC39 chose Set.size() method and not .length property?
Russ on September 25th, 2012 at 10:20 am
It’s possible to use Set and the other (more useful) collections today. I recently updated my Harmony Collections shim with experimental IE8 support. IE9+ are fully supported. http://benvie.github.com/harmony-collections/
Brandon Benvie on September 25th, 2012 at 11:27 am
The reason it’s .size() instead of .length is because items with a length property are expected to also have indexed properties (excluding functions). Over time this has become a defacto standard/norm as all the DOM interfaces that have lengths are indexed, Typed Arrays, regular arrays, strings even. Collections won’t have indexed properties and aren’t iterable using for…in.
Brandon Benvie on September 25th, 2012 at 1:28 pm
It’s worth noting that you’re basically documenting Firefox’s implementation, not what’s on the harmony-namespaced wiki page. Note that the draft spec does not have Set at all so it’s technically not in ES6 yet and we don’t know what the final form will be; Chrome’s `size()`-less version is just as valid.
Also, “internally, the comparison is done using ===” is false, otherwise you could never find `NaN` in the set since nothing is `=== NaN`. Instead it is done with the SameValue algorithm.
Domenic Denicola on September 25th, 2012 at 7:50 pm
It is also worth noting that forEach will eventually work too. The callback has the same signature as the callback to Array.prototype.forEach with the gotcha that the value is passed twice.
Set is ordered by design to allow certain scenarios like work queues.
Erik Arvidsson on September 25th, 2012 at 8:31 pm
The `.length` property is usually writable, so you can potentially remove items from the array by setting a new length value, that’s probably why they decided to create the method `size()` instead.
TBH I’m not a big fan of this addition, Sets are useless without `for of` or the new ES5/ES6 methods/expressions that expects iterables, which means it can’t be properly shimmed. I also agree that the Set provide very few methods compared to other languages.
The name `delete()` is terrible specially since we already have the `delete` keyword in JS. I would name it `remove()` instead (remove is the direct opposite of `add` in this context).
For now I will keep using some functional library like amd-utils to achieve the “same” effect: https://gist.github.com/3788437
Miller Medeiros on September 26th, 2012 at 8:19 am
@Dominic – the only part that is currently Firefox specific is
size(), everything else lines up with the strawman.Nicholas C. Zakas on September 26th, 2012 at 10:21 am
Sets are not entirely useless, even without the ability to iterate them
function unique(array) {
var seen = new Set;
return array.filter(function(item){
if (!seen.has(item)) {
seen.add(item);
return true;
}
});
}
This will be O(n) for filtering uniques in an array. Almost all methods of array unique with objects are O(n^2). (here’s the one that isn’t http://stackoverflow.com/questions/840781/easiest-way-to-find-duplicate-values-in-a-javascript-array/7928115#7928115)
Brandon Benvie on September 27th, 2012 at 12:40 am
Comments are automatically closed after 14 days.