如何用WeakMap/WeakSet实现可回收的JavaScript享元对象?
Great question! The core problem with your current implementation is that Map holds strong references to all your FlyweightNumber instances—even when no other code references them, they’ll never be garbage collected.
You’re right that WeakMap/WeakSet enable automatic garbage collection, but there’s a catch: WeakMap only accepts objects as keys, and your flyweight uses primitive numbers as identifiers. We can’t directly use a WeakMap to map numbers to instances. Instead, we’ll combine a regular Map for fast lookups with a FinalizationRegistry (an ES2021 feature) to clean up the Map when instances are garbage collected.
Here’s the revised implementation:
const FlyweightNumber = (function(){ "use strict"; const instances = new Map(); // Create a registry to clean up the Map when instances are GC'd const cleanupRegistry = new FinalizationRegistry((number) => { instances.delete(number); }); class FlyweightNumber{ constructor(number){ // Check if we already have an instance for this number const existingInstance = instances.get(number); if (existingInstance) { return existingInstance; } // If not, create a new instance Object.defineProperty(this, 'number', { value: number }); instances.set(number, this); // Register the instance with the cleanup registry cleanupRegistry.register(this, number); } toString() { return this.number.toString(); } valueOf(){ return this.number; } toJSON(){ return this.number; } } return FlyweightNumber; })(); module.exports = FlyweightNumber;
How This Works:
- Fast Lookup with Map: We still use a regular
Mapto quickly find existing instances by their number—this keeps the flyweight pattern’s performance benefits. - Automatic Cleanup with FinalizationRegistry: When a
FlyweightNumberinstance is no longer referenced anywhere else in your code, the garbage collector will eventually collect it. At that point, the registry’s callback runs and removes the corresponding number entry from theinstancesMap. - Preserved Behavior: The
toString,valueOf, andtoJSONmethods are kept intact so your flyweight instances behave just like regular numbers in most contexts.
Example Usage:
let numA = new FlyweightNumber(42); let numB = new FlyweightNumber(42); console.log(numA === numB); // true (same instance) // Remove all external references numA = null; numB = null; // After garbage collection runs, instances.get(42) will return undefined // Creating a new FlyweightNumber(42) will generate a fresh instance
Why Not WeakMap Directly?
WeakMap requires objects as keys, so we can’t use primitive numbers to look up instances. The FinalizationRegistry workaround lets us keep the primitive number lookup while still enabling proper garbage collection.
This implementation works in all modern browsers and Node.js versions that support ES2021 or later.
内容的提问来源于stack exchange,提问作者Paulo Henrique

