Skip to content

ES6 - Collections

ES6+ - Modern Collections: Map, Set, WeakMap, WeakSet

Section titled “ES6+ - Modern Collections: Map, Set, WeakMap, WeakSet”

ES6 introduced powerful new built-in data structures: Map, Set, WeakMap, and WeakSet. These provide more versatile ways to organize and manage collections of data compared to traditional objects and arrays.

The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value. This is a significant advantage over plain objects, where keys are implicitly converted to strings.

Syntax: `new Map([iterable])`
Where `iterable` is an Array or other iterable object whose elements are key-value pairs (2-element Arrays like `[key, value]`).
  • map.size: Returns the number of key/value pairs in the Map.
  • map.set(key, value): Adds or updates an element with a specified key and value. Returns the Map object, allowing chaining.
  • map.get(key): Returns the value associated with the key, or undefined if the key can’t be found.
  • map.has(key): Returns a boolean indicating whether an element with the specified key exists.
  • map.delete(key): Removes the element with the specified key. Returns true if an element existed and was removed, otherwise false.
  • map.clear(): Removes all key/value pairs from the Map object.
  • map.keys(): Returns a new Iterator object that contains the keys for each element in the Map in insertion order.
  • map.values(): Returns a new Iterator object that contains the values for each element in the Map in insertion order.
  • map.entries(): Returns a new Iterator object that contains an array of [key, value] for each element in the Map in insertion order. This is the default iterator for Maps.

map.forEach(callbackFn[, thisArg]): Executes callbackFn once for each key-value pair in the Map, in insertion order.

const userRoles = new Map();
// Set key-value pairs
userRoles.set('admin', 'Administrator Access');
userRoles.set('editor', 'Content Editor Access');
userRoles.set(123, 'Numeric Key Example'); // Keys can be non-strings
const user = { id: 1 };
userRoles.set(user, 'Object Key Example'); // Keys can be objects
console.log(userRoles.get('admin')); // Output: Administrator Access
console.log(userRoles.get(user)); // Output: Object Key Example
console.log(userRoles.has('guest')); // Output: false
console.log(userRoles.size); // Output: 4
// Initialize with an array of key-value pairs
const settings = new Map([
['theme', 'dark'],
['fontSize', 16]
]);
console.log(settings.get('theme')); // Output: dark
// Iterating over a Map using for...of (default is entries)
for (const [key, value] of userRoles) {
console.log(`${typeof key} ${key}: ${value}`);
}
// Example output for one entry: string admin: Administrator Access
// Iterating using forEach
settings.forEach((value, key) => {
console.log(`Setting - ${key}: ${value}`);
});
// Output:
// Setting - theme: dark
// Setting - fontSize: 16

A WeakMap is a collection of key/value pairs where the keys must be objects and the values can be arbitrary. The references to the keys are held “weakly,” meaning that if there are no other references to a key object, that object can be garbage collected, and its corresponding entry in the WeakMap will be removed automatically. This makes WeakMaps useful for associating data with objects without preventing those objects from being garbage collected.

Key characteristics of WeakMap:

  • Keys must be objects. Primitive values are not allowed as keys.
  • Entries are not enumerable. You cannot iterate over a WeakMap (no keys(), values(), entries(), forEach(), or size property).
  • Only get(key), set(key, value), has(key), and delete(key) methods are available.
let activeUsers = new WeakMap();
let user1 = { name: 'Alice' };
let user2 = { name: 'Bob' };
activeUsers.set(user1, { lastLogin: Date.now() });
activeUsers.set(user2, { status: 'online' });
console.log(activeUsers.has(user1)); // Output: true
console.log(activeUsers.get(user1)); // Output: { lastLogin: ... }
// If user1 becomes eligible for garbage collection (e.g., user1 = null and no other references exist),
// its entry in activeUsers will eventually be removed by the garbage collector.
user1 = null;
// At some point, activeUsers.has(the_original_user1_object) would be false.

The Set object lets you store unique values of any type, whether primitive values or object references. Values in a Set are ordered by insertion.

Syntax: `new Set([iterable])`
Where `iterable` is an Array or other iterable object whose elements are used to initialize the set.
  • set.size: Returns the number of unique values in the Set.
  • set.add(value): Appends value to the Set. Returns the Set object (chainable). If value already exists, nothing changes.
  • set.has(value): Returns a boolean indicating whether an element with the specified value exists.
  • set.delete(value): Removes the specified value from the Set. Returns true if value was found and removed, otherwise false.
  • set.clear(): Removes all elements from the Set object.
  • set.values(): Returns a new Iterator object that contains the values for each element in the Set in insertion order. This is the default iterator.
  • set.keys(): An alias for values(), behaving identically.
  • set.entries(): Returns a new Iterator object that contains an array of [value, value] for each element in the Set (to maintain consistency with Map).

set.forEach(callbackFn[, thisArg]): Executes callbackFn once for each value in the Set, in insertion order. The callback receives (value, key, set) where key is the same as value.

const uniqueNumbers = new Set([1, 2, 2, 3, 4, 4, 4]);
console.log(uniqueNumbers.size); // Output: 4 (duplicates are ignored)
uniqueNumbers.add(5);
uniqueNumbers.add(1); // Already exists, no change
console.log(uniqueNumbers.has(3)); // Output: true
uniqueNumbers.delete(2);
console.log(uniqueNumbers.has(2)); // Output: false
// Iterating over a Set using for...of (default is values)
for (const number of uniqueNumbers) {
console.log(number);
}
// Output (in insertion order, e.g.): 1, 3, 4, 5
// Iterating using forEach
uniqueNumbers.forEach(value => {
console.log(`Set value: ${value}`);
});

A WeakSet is a collection of objects only. Similar to WeakMap, references to objects in a WeakSet are held “weakly.” If an object in a WeakSet has no other references, it can be garbage collected.

Key characteristics of WeakSet:

  • Can only store objects. Primitive values are not allowed.
  • Entries are not enumerable. You cannot iterate over a WeakSet (no iterators, forEach, or size).
  • Only add(value), has(value), and delete(value) methods are available.
let processedObjects = new WeakSet();
let obj1 = { id: 'A' };
let obj2 = { id: 'B' };
function processObject(obj) {
if (!processedObjects.has(obj)) {
console.log(`Processing object: ${obj.id}`);
// ... perform processing ...
processedObjects.add(obj);
} else {
console.log(`Object ${obj.id} already processed.`);
}
}
processObject(obj1);
processObject(obj2);
processObject(obj1); // Will indicate it's already processed
// If obj1 becomes eligible for GC, its entry in processedObjects is removed.
obj1 = null;

All standard collections (Array, Map, Set, String) in ES6+ are iterable. This means they implement the iterator protocol. An iterator is an object that knows how to access items from a collection one at a time, while keeping track of its current position within that sequence. Iterators have a next() method which returns an object with two properties: value (the next item in the sequence) and done (a boolean that is true if the iterator has reached the end of the sequence).

const myMap = new Map([['a', 1], ['b', 2]]);
const mapIterator = myMap.entries(); // Get an iterator
console.log(mapIterator.next()); // { value: ['a', 1], done: false }
console.log(mapIterator.next()); // { value: ['b', 2], done: false }
console.log(mapIterator.next()); // { value: undefined, done: true }
const mySet = new Set(['apple', 'banana']);
const setIterator = mySet.values();
console.log(setIterator.next()); // { value: 'apple', done: false }
console.log(setIterator.next()); // { value: 'banana', done: false }
console.log(setIterator.next()); // { value: undefined, done: true }

While manual iteration is possible, the for...of loop is the most common and convenient way to work with iterables.