InterviewHubPRO
Section Overview
Q488

Question 488 of 521

Part 8: Modern JavaScript APIs, WeakRef, Proxy & Performance (464 - 521)

94%
JavaScript Internals (521 Qs)•Part 8: Modern JavaScript APIs, WeakRef, Proxy & Performance (464 - 521)

What are WeakRef and FinalizationRegistry used for?

WeakRef and FinalizationRegistry are advanced features (introduced in ES2021) for managing memory and object lifecycles in JavaScript. They provide low-level control over garbage collection behavior.

WeakRef (Weak Reference):

A WeakRef creates a weak reference to an object, meaning it doesn't prevent the object from being garbage collected. Unlike regular references, holding a WeakRef doesn't keep the object alive.

Syntax:

javascript
 const weakRef = new WeakRef(targetObject);
 const obj = weakRef.deref(); // Get the object (or undefined if collected)

WeakRef Examples:

javascript
 // Creating a weak reference
 let obj = { name: 'Important Data', value: 42 };
 const weakRef = new WeakRef(obj);

 // Access the object
 console.log(weakRef.deref()); // { name: 'Important Data', value: 42 }

 // Remove strong reference
 obj = null;

 // At some point, after garbage collection
 console.log(weakRef.deref()); // undefined (object was collected)

 // Practical use: Caching without memory leaks
 class ImageCache {
   #cache = new Map();

   getImage(url) {
     const weakRef = this.#cache.get(url);
     if (weakRef) {
       const image = weakRef.deref();
       if (image) {
         console.log('Cache hit!');
         return image;
       }
     }

     // Load image if not in cache or was collected
     console.log('Cache miss, loading...');
     const newImage = this.loadImage(url);
     this.#cache.set(url, new WeakRef(newImage));
     return newImage;
   }

   loadImage(url) {
     // Simulate loading
     return { url, data: `Image data for ${url}` };
   }
 }

 const cache = new ImageCache();
 const img1 = cache.getImage('photo.jpg'); // Cache miss
 const img2 = cache.getImage('photo.jpg'); // Cache hit!

FinalizationRegistry:

FinalizationRegistry allows you to register callbacks that run after objects are garbage collected. This enables cleanup actions when objects are no longer needed.

Syntax:

javascript
 const registry = new FinalizationRegistry((heldValue) => {
   // Cleanup callback when object is garbage collected
   console.log('Cleaning up:', heldValue);
 });

 registry.register(targetObject, heldValue, unregisterToken);

FinalizationRegistry Examples:

javascript
 // Basic usage
 const registry = new FinalizationRegistry((filename) => {
   console.log(`File ${filename} can be deleted - object was collected`);
   // Perform cleanup: close file handles, free resources, etc.
 });

 let fileObject = { name: 'temp.txt', handle: 'handle123' };
 registry.register(fileObject, 'temp.txt');

 // When fileObject is garbage collected, the callback runs
 fileObject = null; // Remove strong reference

 // Real-world example: Resource management
 class FileManager {
   #registry = new FinalizationRegistry((filepath) => {
     this.#closeFile(filepath);
   });

   #openFiles = new Map();

   openFile(filepath) {
     const handle = this.#actuallyOpenFile(filepath);
     const file = { filepath, handle };
     
     this.#openFiles.set(filepath, handle);
     this.#registry.register(file, filepath);
     
     return file;
   }

   #actuallyOpenFile(filepath) {
     console.log(`Opening ${filepath}`);
     return { /* file handle */ };
   }

   #closeFile(filepath) {
     const handle = this.#openFiles.get(filepath);
     if (handle) {
       console.log(`Auto-closing ${filepath}`);
       // Close file handle
       this.#openFiles.delete(filepath);
     }
   }
 }

 // Database connection pooling
 class ConnectionPool {
   #registry = new FinalizationRegistry((connectionId) => {
     console.log(`Connection ${connectionId} released`);
     this.#releaseConnection(connectionId);
   });

   #connections = new Map();

   getConnection() {
     const connectionId = Math.random().toString(36);
     const connection = { id: connectionId, query: () => {} };
     
     this.#connections.set(connectionId, connection);
     this.#registry.register(connection, connectionId);
     
     return connection;
   }

   #releaseConnection(connectionId) {
     this.#connections.delete(connectionId);
     // Return connection to pool
   }
 }

 // Using unregister token to prevent cleanup
 const cleanupRegistry = new FinalizationRegistry((msg) => {
   console.log('Cleanup:', msg);
 });

 let importantObj = { data: 'important' };
 const token = {}; // Unregister token

 cleanupRegistry.register(importantObj, 'important data', token);

 // Later, if you want to prevent cleanup
 cleanupRegistry.unregister(token); // Callback won't run even after GC

Combined Example - Cache with Cleanup:

javascript
 class SmartCache {
   #cache = new Map();
   #registry = new FinalizationRegistry((key) => {
     console.log(`Removing cache entry: ${key}`);
     this.#cache.delete(key);
   });

   set(key, value) {
     const weakRef = new WeakRef(value);
     this.#cache.set(key, weakRef);
     this.#registry.register(value, key, weakRef);
   }

   get(key) {
     const weakRef = this.#cache.get(key);
     if (!weakRef) return undefined;

     const value = weakRef.deref();
     if (value === undefined) {
       // Object was collected, clean up map
       this.#cache.delete(key);
     }
     return value;
   }

   has(key) {
     return this.get(key) !== undefined;
   }

   delete(key) {
     const weakRef = this.#cache.get(key);
     if (weakRef) {
       this.#registry.unregister(weakRef);
       this.#cache.delete(key);
     }
   }
 }

 const cache = new SmartCache();
 let data = { huge: 'dataset' };
 cache.set('myData', data);
 
 console.log(cache.get('myData')); // { huge: 'dataset' }
 
 data = null; // Remove strong reference
 // After GC, cache entry is automatically cleaned up

Important Caveats:

  1. Non-Deterministic: Garbage collection timing is unpredictable
  2. No Guarantees: The finalization callback may never run (e.g., if the process exits)
  3. Performance: These are advanced features; use only when necessary
  4. Avoid Over-Use: Regular JavaScript patterns are usually better
  5. Not for Critical Logic: Don't rely on finalization for business logic

When to Use:

  • ✅ Caching large objects that can be recreated
  • ✅ Managing native resources (file handles, sockets)
  • ✅ Automatic cleanup of external resources
  • ✅ Memory-sensitive applications
  • ❌ Not for regular object lifecycle management
  • ❌ Not for critical cleanup (use explicit cleanup instead)