The ES6 release introduced two new JavaScript objects - Set and WeakSet, which are very similar to Arrays in that they allow elements to be stored.
Unlike Arrays, however, Sets and WeakSets cannot store duplicate values.
Let's start learning how to work with both.
A Set is an object that contains a collection of unique values. Each value can only occur once.
It remembers the insertion order and allows you to store values of any type.
// Create a new Set
const users = new Set();
An element can be added to the Set by using the add(value)
function.
Returns Set object:
users.add(0); // Set { 0 }
users.add("John"); // Set { 0, "John" }
const obj = { name: "John" };
users.add(obj); // Set { 0, "John", { name: "John" } }
// Function
const func = () => "John";
users.add(func); // Set { 0, "John", { name: "John" }, [Function: func] }
// NaN
users.add(NaN); // Set { 0, "John", { name: "John" }, [Function: func], NaN }
// Undefined
users.add(undefined); // Set { 0, "John", { name: "John" }, [Function: func], NaN, undefined }
Important note: NaN and undefined can also occur in Sets. All NaN values are equated, even though in JavaScript NaN !== NaN
.
To check whether a Set contains a value, has(key)
function can be used.
Returns true or false:
users.has(0); // true
users.has("John"); // true
users.has(obj); // true
users.has(func); // true
users.has(NaN); // true
users.has(undefined); // true
users.has("Not existing item"); // false
An element can be removed from the Set by using the delete(key)
function.
Returns true if an element has been removed, false if it does not exist:
users.delete(0); // true
users.delete("John"); // true
users.delete(obj); // true
users.delete(func); // true
users.delete(NaN); // true
users.delete(undefined); // true
users.delete("Not existing item"); // false
To remove all values from the Set object, a clear()
method can be used:
users.clear();
console.log(users); // Set {}
To determine the size of the Set, an instance property size
can be accessed.
Returns the number of elements:
const projects = new Set();
projects.add("project1");
projects.add("project2");
projects.size; // 2
To get all keys or values of the Set, a keys()
or values()
method is used accordingly.
For Sets, both methods behave exactly the same way, returning stored elements.
Returns an Iterator object that contains all keys or values in an insertion order:
projects.keys(); // [Set Iterator] { "project1", "project2" }
projects.values(); // [Set Iterator] { "project1", "project2" }
To get all entries of the Set, the entries()
function is used.
Remember that keys and values are equal in Sets.
Returns an Iterator object that containing an array of all [key, value] pairs in an insertion order:
// [Set Entries] {
// [ "project1", "project1" ],
// [ "project2", "project2" ]
// }
projects.entries();
To iterate over all elements, a forEach(callbackFn[, thisArg])
method is used:
Calls callbackFn for each value in insertion order. If a thisArg is specified, it is used as this value for each callback:
// "project1" "project1"
// "project2" "project2"
projects.forEach((value, key) => {
console.log(key, value);
});
The main differences between the two:
Indexed collection is a collection of data ordered by an index value.
Keyed collection is a collection of data indexed by a key.
There are several ways to remove duplicates from an Array, but one of the simplest and cleanest is to use Set object:
const users = ["John", "Andrew", "Mike", "John", "John"];
const uniqueUsers = [...new Set(users)];
console.log(uniqueUsers); // [ "John", "Andrew", "Mike" ]
A WeakSet is similar to the Set with some important differences:
An example of a WeakSet:
const users = new WeakSet();
const john = { id: 1, name: "John" };
const andrew = { id: 2, name: "Andrew" };
users.add(john); // OK
users.add(andrew); // OK
// TypeError: Invalid value used in weak set
users.add("John");
If an object is added to the WeakSet and there are no other references to this object, it is automatically garbage-collected (removed from the WeakSet and the memory):
const users = new WeakSet();
let john = { id: 1, name: "John" };
users.add(john);
// "John" is removed both, from the memory and the WeakSet
john = null;
Compare it to the Set:
const users = new Set();
let john = { id: 1, name: "John" };
users.add(john);
// John is removed from the memory, but not from the Set
john = null;
// Set { { id: 1, name: "John" } }
console.log(users);
WeakSet only supports the following methods:
add(key)
has(key)
delete(key)
This means that it is impossible to iterate or loop the keys, values and entries, calculate the size or delete all values of a WeakSet.
To understand that restrictions are necessary, we need to know how the garbage-collection works in JavaScript.
When an object has lost all its references, it has to be garbage-collected automatically, but we do not know when exactly when it will happen.
It is decided by the JavaScript engine. The cleanup can be done immediately or after some time.
Since an object is also deleted from the WeakSet, we do not exactly know what elements it contains, so we cannot calculate the size or iterate over its elements.
WeakSets can be used to tag objects without mutating them:
let isMarked = new WeakSet();
class Node {
constructor(id) {
this.id = id;
}
mark() {
isMarked.add(this);
}
};
let node1 = new Node("node1");
node1.mark();
isMarked.has(node1); // true
node1 = null;
isMarked.has(node1); // false
Some more examples are to be found in this discussion.
In this article we got to know Set and WeakSet in JavaScript, talked about the differences between Array and Set and found out a good way to remove duplicates from an Array.
Both objects can be considered as alternatives to Arrays.
Try them out and see what benefits they can bring to your project.