Skip to content
Lesson 4 of 8

Step 1 of 5 · Reading · ~4 min

Learn

Proxy and Memory

WeakRef, WeakMap, WeakSet

A garbage collector frees an object only when nothing can reach it. A normal Map or Set reaches everything stored in it, so a cache keyed by objects keeps those objects alive for as long as the cache lives. Weak collections hold their keys without keeping them alive. By the end you can attach data to objects you do not own, and know what you may and may not rely on.

The leak a Map causes

javascript

Every el ever passed to track stays in memory, even after the rest of the program has dropped it. The fix is not to remember to delete. It is to use a collection whose reference does not count.

WeakMap: data keyed by object identity

javascript

Only objects (and functions) can be keys, because only they have an identity the collector can track. A WeakMap has no size, no keys(), no iteration: an entry may disappear at any moment after its key becomes unreachable, so any count or listing would expose when the collector ran. If the key is already unreachable, no code could look the entry up anyway; if it is reachable, you hold it and can call get.

Two properties follow. The map does not keep the key alive, and a value that refers back to its own key does not keep it alive either. And the cache is by identity, not content: mutating the key does not invalidate the entry.

Private data without a leak

A module-level WeakMap can hold per-instance state that never appears on the instance:

javascript

When acct is dropped, its state goes with it. (Private #fields do the same job in new code; the WeakMap pattern is what you meet in older libraries, and it still works for data about objects you did not create.)

WeakSet: a mark that does not pin the object

A WeakSet stores only membership. It is the right tool for "have I already visited this node?" in a graph with cycles, because the marks vanish with the nodes:

javascript

WeakRef and FinalizationRegistry

new WeakRef(obj).deref() returns the object if it is still alive and undefined once it has been collected. new FinalizationRegistry(cb) calls cb(heldValue) some time after an object registered with it is collected. Both are for caches and diagnostics, not for logic: collection timing is not specified, may never happen before the program exits, and differs between engines. Always keep the result of deref() in a variable and test it, never call it twice and assume the same answer. They arrived in Node 14.6, so the grader's Node 12 has neither; test with typeof WeakRef === 'function' before relying on them.

Common mistakes

  • Using a Map for per-object metadata and calling it a cache. It is a leak with extra steps.
  • Trying to read .size or loop over a WeakMap. Neither exists; if you need to enumerate, use a Map.
  • Passing a primitive or null as a key. A truthiness check on typeof is not enough, since typeof null is 'object'.
  • Treating a falsy cached result as a miss: if (cache.get(k)) re-computes every time the answer is 0 or ''. Use has.

Your exercise

You will write a memoizer that caches per argument object. The starter's area counts how often it really runs, so a cache that misses, caches the wrong thing, or crashes on null shows up in the numbers.

Up nextEvent Loop and MicrotasksThe Runtime

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…