WeakMaps and WeakSets
Garbage collection
In JavaScript values in objects are not garbage collected when all references outside the object are lost. As long as the object is reachable, its properties and elements are reachable. This potentially causes a memory leak. This also hold for maps and sets.
let myKey = { a: 1 };
const myMap = new Map([
[myKey, "some value"],
]);
myKey = {};
// All { a: 1 } references outside the map are lost.
// Yet, { a: 1 } is still reachable:
for (const [key, value] of myMap) {
console.log(key);
}
// logs:
// { a: 1 }
In the example above, object { a: 1 }
and the value associated with that key in the map will not be garbage collected.
This is a memory leak. If nothing else in the program is referencing the key, the key and associated value should be eligible for garbage collection.
Weak referenced keys
The WeakMap
and WeakSet
objects closely mirror their non-weak counterparts Map
and Set
.
Only now keys are weakly referenced, meaning that the
mark-and-sweep algorithm
won't consider keys as reachable if nothing else strongly refers to them. Now there is no memory leak.
Note that keys can only weakly reference an object. In JavaScript primitive values are not referenced, but (strongly) stored in a variable, or in this case in a key. In case of primitive valued keys, the weak referenced key removal would never trigger, making them stay in the collection forever.
let myKey = { a: 1 };
const myMap = new WeakMap([
[myKey, "some value"],
]);
myKey = {};
// Now object { a: 1 } and the associated value are subject to garbage collection!
let myKey = "someKey";
const myMap = new WeakMap([
[myKey, "some value"],
]); // TypeError: WeakMap key must be an object
WeakMap
and WeakSet
are not iterable
Garbage collection happens automatically.
JavaScript does not provide a way to programmatically trigger garbage collection.
It is not specified when unreachable values are removed from memory, it depends on how the algorithm is implemented in the JavaScript engine.
This means that methods and operations that need to consider the number and order of elements in the collection cannot be reliable.
Therefore WeakMap
and WeakSet
do not have a size
method and iteration over the elements is not possible.
WeakMap
only has the methods set(key, value)
, get(key)
, delete(key)
and has(key)
.
WeakSet
only has the methods add(value)
, delete(value)
and has(value)
.
let myKey = { a: 1 };
const myMap = new WeakMap([
[myKey, "some value"],
[[2], "..."],
]);
for (const [key, value] of myMap) {
console.log(key);
} // TypeError: myMap is not iterable
Note that get(key)
and has(key)
will not work when objects have no strong references anymore,
regardless whether garbage collection occurred or not:
let myKey = { a: 1 };
const myMap = new Map([ // not a WeakMap, so no garbage collection
[myKey, "some value"],
[[2], "..."],
]);
myKey = null;
console.log(myMap.get(myKey)); //logs: undefined // key null does not exist
console.log(myMap.get({ a: 1 })); //logs: undefined // { a: 1 } !== { a: 1 }
Examples
In the next example a function useObj()
counts how many times it is called on a particular object and
stores this count per object in a WeakMap
.
const callCount = new WeakMap();
function useObj(obj){
// do whatever with obj
if (!callCount.has(obj)) {
obj.status = "active";
}
// update callCount:
let called = callCount.get(obj) || 0;
called++; // called one more time
callCount.set(obj, called);
}
// a temporary object is used a number of times throughout the program via useObj():
let tempObj = { name: "some temporary object" };
useObj(tempObj);
useObj(tempObj);
useObj(tempObj);
console.log(`Object "${tempObj.name}" was used ${callCount.get(tempObj)} times`); // logs: 'Object "some temporary object" was used 3 times'
// the object is not needed any more:
tempObj = null;
// Object { name: "some temporary object" } and the associated value are subject to garbage collection.
The above example is an adaptation of this Stackoverflow answer.
WeakSet
has only the has()
method to read the collection. This makes a WeakSet
suitable for tracking the existence of temporary objects.
const mySet = new WeakSet();
let tempObj1 = {},
tempObj2 = {},
tempObj3 = ["..."],
tempObj4 = {};
mySet.add(tempObj1);
mySet.add(tempObj2);
mySet.add(tempObj3);
mySet.add(tempObj4);
console.log(mySet.has(tempObj3)); // logs: true
tempObj3 = null;
console.log(mySet.has(tempObj3)); // logs: false
// Array ["..."] is now queued up for the trash can.