Levix

Levix's zone

x
telegram

JavaScript Sets will soon support more operations such as union, intersection, and difference.

JavaScript Set has long been regarded as incomplete since its introduction in the ES2015 specification. However, this situation is about to change.

Sets are a collection type that ensures the values within them are unique and non-repeating. In the ES2015 version, the functionality provided by Set was mainly limited to creating, adding, deleting elements, and checking whether an element belongs to a Set. If operations or comparisons between multiple sets were needed, custom functions had to be written. Fortunately, the committee responsible for the ECMAScript specification, TC39, along with major browser developers, has made progress in this area. We can now perform operations such as union, intersection, and difference in JavaScript.

Before diving into these new features, let's first review what existing JavaScript Sets can do, and then discuss the new Set functions and the JavaScript engines that support these features.

What operations can JavaScript Sets in ES2015 perform?#

Exploring the basic functionality of JavaScript Set through examples is the most straightforward approach.

You can create an empty Set, or initialize a Set by providing an iterable object (like an array).

const languages = new Set(["JavaScript", "TypeScript", "HTML", "JavaScript"]);

Since the values in a Set must be unique, the above Set actually contains three elements. This can be confirmed by the size property of the Set.

languages.size;

// => 3

You can add new elements to the Set using the add method. If you try to add an element that already exists, there will be no change.

languages.add("JavaScript");

languages.add("CSS");

languages.size;

// => 4

You can remove elements from the Set using the delete method.

languages.delete("TypeScript");

languages.size;

// => 3

You can check whether an element belongs to the Set using the has method. Compared to arrays, Set is more efficient in this regard, as this operation has a constant time complexity (O(1)).

languages.has("JavaScript");

// => true

languages.has("TypeScript");

// => false

You can also iterate over the elements of the Set using forEach or for...of loops. The order of elements is based on the order they were added to the Set.

languages.forEach(element => console.log(element));

// "JavaScript"

// "HTML"

// "CSS"

Additionally, you can obtain iterators from the Set using the keys, values (which is actually equivalent to keys), and entries methods.

Finally, you can clear a Set using the clear method.

languages.clear();

languages.size;

// => 0

This is a simple review of the operations that can be performed using the Set from the ES2015 specification:

  • Set provides methods for handling collections of unique values.
  • Adding elements to a Set and testing for the existence of elements in a Set are very efficient.
  • Converting an Array or other iterable objects to a Set is a convenient way to remove duplicates.

However, this implementation lacks the ability to perform operations between Sets. You might want to merge two Sets to create a new Set containing all elements from both (union), find common elements between two Sets (intersection), or determine elements that are in one Set but not in another (difference). Until recently, implementing these operations required custom functions.

What do the new Set functions include?#

The proposal for Set methods adds the following methods to Set instances: union, intersection, difference, symmetricDifference, isSubsetOf, isSupersetOf, and isDisjointFrom.

Some of these methods are similar to certain join operations in SQL, and we will demonstrate the purpose of each function through code examples.

The following code examples can be tried in Chrome 122+ or Safari 17+.

Set.prototype.union(other)#

The union of two sets is a set that contains all elements from both sets.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const allLanguages = frontEndLanguages.union(backEndLanguages);

// => Set {"JavaScript", "HTML", "CSS", "Python", "Java"}

In this example, all languages from the first and second sets appear in the third set. Like other methods for adding elements to a Set, duplicate elements are automatically removed.

This is equivalent to performing a SQL FULL OUTER JOIN on two tables.

Venn diagram of union showing all contents from both sets selected.

Set.prototype.intersection(other)#

The intersection of two sets is a set that contains the common elements from both sets.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const frontAndBackEnd = frontEndLanguages.intersection(backEndLanguages);

// => Set {"JavaScript"}

Here, "JavaScript" is the only element that appears in both sets.

The intersection is equivalent to SQL's INNER JOIN.

Venn diagram showing the intersection of two sets. Only the items common to both sets are selected.

Set.prototype.difference(other)#

The difference between the set performing the operation and another set contains all elements unique to the first set.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const onlyFrontEnd = frontEndLanguages.difference(backEndLanguages);

// => Set {"HTML", "CSS"}

const onlyBackEnd = backEndLanguages.difference(frontEndLanguages);

// => Set {"Python", "Java"}

When determining the difference between two sets, the order of the sets is very important. In the above example, removing the back-end languages from the front-end languages results in "JavaScript" being removed, leaving "HTML" and "CSS". Conversely, removing the front-end languages from the back-end languages still removes "JavaScript", leaving "Python" and "Java".

The difference is similar to performing a SQL LEFT JOIN.

Venn diagram showing the difference between two sets, only the items unique to the first set are selected.

Set.prototype.symmetricDifference(other)#

The symmetric difference of two sets is a set that contains all elements unique to both sets.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const onlyFrontEnd = frontEndLanguages.symmetricDifference(backEndLanguages);

// => Set {"HTML", "CSS", "Python", "Java"}

const onlyBackEnd = backEndLanguages.symmetricDifference(frontEndLanguages);

// => Set {"Python", "Java", "HTML", "CSS"}

In this case, although the elements in the result set are the same, the order of the elements differs based on the calling set. The order of elements is determined by the order they were added to the set, and the elements of the set performing the operation are added first.

The symmetric difference is similar to SQL's FULL OUTER JOIN excluding common elements from both tables.

Venn diagram of symmetric difference showing all unique contents from both sets.

Set.prototype.isSubsetOf(other)#

If all elements of the first set appear in the second set, then the first set is a subset of the second set.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const declarativeLanguages = new Set(["HTML", "CSS"]);

declarativeLanguages.isSubsetOf(frontEndLanguages);

// => true

frontEndLanguages.isSubsetOf(declarativeLanguages);

// => false

Any set is a subset of itself.

frontEndLanguages.isSubsetOf(frontEndLanguages);

// => true

Set.prototype.isSupersetOf(other)#

If the first set contains all elements of the second set, then the first set is a superset of the second set. This is the opposite relationship of being a subset.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const declarativeLanguages = new Set(["HTML", "CSS"]);

declarativeLanguages.isSupersetOf(frontEndLanguages);

// => false

frontEndLanguages.isSupersetOf(declarativeLanguages);

// => true

Any set is a superset of itself.

frontEndLanguages.isSupersetOf(frontEndLanguages);

// => true

Set.prototype.isDisjointFrom(other)#

If two sets have no common elements, they are disjoint.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const interpretedLanguages = new Set(["JavaScript", "Ruby", "Python"]);

const compiledLanguages = new Set(["Java", "C++", "TypeScript"]);

interpretedLanguages.isDisjointFrom(compiledLanguages);

// => true

frontEndLanguages.isDisjointFrom(interpretedLanguages);

// => false

In these examples, the sets of interpreted languages and compiled languages have no intersection, so they are disjoint. However, the sets of front-end languages and interpreted languages are not disjoint because they share the element "JavaScript".

Support Status#

As of the writing of this article, these new Set methods proposal have entered stage 3 of the TC39 standardization process, with Safari 17 (released in September 2023) and Chrome 122 (expected in February 2024) implementing these methods. Edge followed closely behind Chrome, while Firefox Nightly has experimental support for these features, and both browsers are expected to support them officially soon.

Bun also uses Safari's JavaScriptCore engine, so it already supports these new features. Chrome's support for these features means they have been integrated into the V8 JavaScript engine and will soon be adopted by Node.js.

This hopefully means that these proposals will smoothly transition to stage 4 of the process and may timely become part of the ES2024 specification.

Polyfills#

If you need support for these features on older JavaScript engines, you can use polyfills. These polyfills can be obtained through core-js or provided as separate packages in the es-shims project (for example, the [set.prototype.union package](https://www.npmjs.com/package/set.prototype.union "set.prototype.union package" for implementing union functionality).

If you have already written your own implementations for these features, it is recommended to migrate to these polyfills first, gradually phasing out custom implementations as support for these features becomes widespread.

The functionality of Sets no longer feels lacking#

JavaScript Set has long been seen as functionally incomplete, but the addition of these seven new functions makes it much more comprehensive. Building such functionality into the language itself means we can reduce reliance on external dependencies or custom implementations and focus more on solving real problems.

This is just a part of the many stage 3 proposals currently under consideration by TC39. Check out this list to see what new features may be added to JavaScript next. I am particularly interested in Temporal and Decorators, as these two proposals could change significant aspects of how we write JavaScript.

Original link

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.