JavaScript WeakRef and FinalizationRegistry: Memory-Safe Caches

WeakRef and FinalizationRegistry let JavaScript hold references to objects without preventing their garbage collection — enabling memory-safe caches, plugin cleanup hooks, and observation patterns that don’t leak memory when WordPress admin components unmount.

Problem: A WordPress admin JavaScript application caches DOM references or API response objects in a Map or plain object — but because the map holds strong references, cached entries for removed DOM nodes or stale data are never garbage-collected, causing a memory leak over time.

Solution: Use WeakRef to hold a non-owning reference to a cached object — the garbage collector can still reclaim it. Pair with FinalizationRegistry to receive a callback when the referenced object is collected, enabling automatic cache eviction. This pattern is ideal for large object caches keyed by DOM nodes.

The examples below build a WeakRef-based DOM element cache that automatically clears when elements are removed, use FinalizationRegistry to log when a block editor component is garbage-collected, and implement a memory-safe event listener cleanup registry.

// WeakRef — holds a reference to an object but does not prevent GC
// .deref() returns the object if still alive, or undefined if collected

// Memory-safe DOM cache: holds element references weakly
// When the element is removed from the DOM and GC'd, the cache entry vanishes automatically
const elementCache = new Map();   // Note: WeakMap would be even better here

function getOrCreateTooltip( triggerEl ) {
    const ref = elementCache.get( triggerEl );
    const existing = ref?.deref();   // deref() returns undefined if GC'd
    if ( existing ) return existing;

    const tooltip = document.createElement( 'div' );
    tooltip.className = 'wp-tooltip';
    document.body.appendChild( tooltip );

    // Store as a WeakRef — tooltip can still be GC'd if removed from DOM
    elementCache.set( triggerEl, new WeakRef( tooltip ) );
    return tooltip;
}

// WeakMap is the preferred tool when the key IS the object you want to track:
const blockData = new WeakMap();

function attachBlockMeta( blockEl, meta ) {
    blockData.set( blockEl, meta );  // no memory leak — GC'd with the element
}

function getBlockMeta( blockEl ) {
    return blockData.get( blockEl );  // undefined if GC'd or never set
}

Use FinalizationRegistry to clean up resources when objects are garbage-collected:

// FinalizationRegistry — runs a callback when a registered object is GC'd
// Useful for closing WebSockets, cancelling intervals, or logging leaks

const registry = new FinalizationRegistry( ( heldValue ) => {
    // heldValue is the "token" you passed at registration time
    // The registered target object is ALREADY GONE when this fires
    console.log( `Block component "${heldValue}" was garbage collected` );

    // Clean up associated resources using the token (not the dead object)
    openConnections.delete( heldValue );
    clearInterval( pollingIntervals.get( heldValue ) );
} );

// Register a block editor component instance for cleanup tracking
function createBlockComponent( clientId ) {
    const component = {
        clientId,
        ws: new WebSocket( 'wss://example.com/live' ),
    };

    // Register component — second arg is the "held value" (survives GC)
    registry.register( component, clientId );
    openConnections.set( clientId, component.ws );

    return component;
}

// Important caveats:
// 1. GC timing is non-deterministic — do NOT rely on FinalizationRegistry for
//    time-sensitive cleanup. Use explicit cleanup functions for that.
// 2. Callbacks may not fire at all in some environments (e.g., tests).
// 3. WeakRef.deref() can return undefined at any point after GC is eligible.
// → Use these APIs for optional optimisations, not critical logic.

NOTE: WeakRef and FinalizationRegistry are supported in all modern browsers and Node.js 14+. The most practical use case in WordPress JavaScript is a WeakMap-based metadata store attached to DOM elements or Gutenberg block objects — it provides O(1) lookup without any manual cleanup code.

Leave Comment

Your email address will not be published. Required fields are marked *