What are the differences between `Map`/`Set` and `WeakMap`/`WeakSet` in JavaScript?
TL;DR
The primary difference between Map
/Set
and WeakMap
/WeakSet
in JavaScript lies in how they handle keys. Here's a breakdown:
Map
vs. WeakMap
Map
s allows any data type (strings, numbers, objects) as keys. The key-value pairs remain in memory as long as the Map
object itself is referenced. Thus they are suitable for general-purpose key-value storage where you want to maintain references to both keys and values. Common use cases include storing user data, configuration settings, or relationships between objects.
WeakMap
s only allows objects as keys. However, these object keys are held weakly. This means the garbage collector can remove them from memory even if the WeakMap
itself still exists, as long as there are no other references to those objects. WeakMap
s are ideal for scenarios where you want to associate data with objects without preventing those objects from being garbage collected. This can be useful for things like:
- Caching data based on objects without preventing garbage collection of the objects themselves.
- Storing private data associated with DOM nodes without affecting their lifecycle.
Set
vs. WeakSet
Similar to Map
, Set
s allow any data type as keys. The elements within a Set
must be unique. Set
s are useful for storing unique values and checking for membership efficiently. Common use cases include removing duplicates from arrays or keeping track of completed tasks.
On the other hand, WeakSet
only allows objects as elements, and these object elements are held weakly, similar to WeakMap
keys. WeakSet
s are less commonly used, but applicable when you want a collection of unique objects without affecting their garbage collection. This might be necessary for:
- Tracking DOM nodes that have been interacted with without affecting their memory management.
- Implementing custom object weak references for specific use cases.
Here's a table summarizing the key differences:
Feature | Map | WeakMap | Set | WeakSet |
---|---|---|---|---|
Key Types | Any data type | Objects (weak references) | Any data type (unique) | Objects (weak references, unique) |
Garbage Collection | Keys and values are not garbage collected | Keys can be garbage collected if not referenced elsewhere | Elements are not garbage collected | Elements can be garbage collected if not referenced elsewhere |
Use Cases | General-purpose key-value storage | Caching, private DOM node data | Removing duplicates, membership checks | Object weak references, custom use cases |
Choosing between them
- Use
Map
andSet
for most scenarios where you need to store key-value pairs or unique elements and want to maintain references to both the keys/elements and the values. - Use
WeakMap
andWeakSet
cautiously in specific situations where you want to associate data with objects without affecting their garbage collection. Be aware of the implications of weak references and potential memory leaks if not used correctly.
Map
/Set
vs WeakMap
/WeakSet
The key differences between Map
/Set
and WeakMap
/WeakSet
in JavaScript are:
- Key types:
Map
andSet
can have keys of any type (objects, primitive values, etc.), whileWeakMap
andWeakSet
can only have objects as keys. Primitive values like strings or numbers are not allowed as keys inWeakMap
andWeakSet
. - Memory management: The main difference lies in how they handle memory.
Map
andSet
have strong references to their keys and values, which means they will prevent garbage collection of those values. On the other hand,WeakMap
andWeakSet
have weak references to their keys (objects), allowing those objects to be garbage collected if there are no other strong references to them. - Key enumeration: Keys in
Map
andSet
are enumerable (can be iterated over), while keys inWeakMap
andWeakSet
are not enumerable. This means you cannot get a list of keys or values from aWeakMap
orWeakSet
. size
property:Map
andSet
have asize
property that returns the number of elements, whileWeakMap
andWeakSet
do not have asize
property because their size can change due to garbage collection.- Use cases:
Map
andSet
are useful for general-purpose data structures and caching, whileWeakMap
andWeakSet
are primarily used for storing metadata or additional data related to objects, without preventing those objects from being garbage collected.
Map
and Set
are regular data structures that maintain strong references to their keys and values, while WeakMap
and WeakSet
are designed for scenarios where you want to associate data with objects without preventing those objects from being garbage collected when they are no longer needed.
Use cases of WeakMap
and WeakSet
Tracking active users
In a chat application, you might want to track which user objects are currently active without preventing garbage collection when the user logs out or the session expires. We use a WeakSet
to track active user objects. When a user logs out or their session expires, the user object can be garbage-collected if there are no other references to it.
const activeUsers = new WeakSet();// Function to mark a user as activefunction markUserActive(user) {activeUsers.add(user);}// Function to check if a user is activefunction isUserActive(user) {return activeUsers.has(user);}// Example usagelet user1 = { id: 1, name: 'Alice' };let user2 = { id: 2, name: 'Bob' };markUserActive(user1);markUserActive(user2);console.log(isUserActive(user1)); // trueconsole.log(isUserActive(user2)); // true// Simulate user logging outuser1 = null;// user1 is now eligible for garbage collectionconsole.log(isUserActive(user1)); // false
Detecting circular references
WeakSet
is provides a way of guarding against circular data structures by tracking which objects have already been processed.
// Create a WeakSet to track visited objectsconst visited = new WeakSet();// Function to traverse an object recursivelyfunction traverse(obj) {// Check if the object has already been visitedif (visited.has(obj)) {return;}// Add the object to the visited setvisited.add(obj);// Traverse the object's propertiesfor (let prop in obj) {if (obj.hasOwnProperty(prop)) {let value = obj[prop];if (typeof value === 'object' && value !== null) {traverse(value);}}}// Process the objectconsole.log(obj);}// Create an object with a circular referenceconst obj = {name: 'John',age: 30,friends: [{ name: 'Alice', age: 25 },{ name: 'Bob', age: 28 },],};// Create a circular referenceobj.self = obj;// Traverse the objecttraverse(obj);