Search code examples
javascriptweak-referencesweakmap

WeakMap inverted


Is there a way to create WeakMap of any other weak references in Javascript for storing key value pairs where key is String/Number and value is Object.

The referencing would have to work something like this:

const wMap = new WeakRefMap();
const referencer = {child: new WeakRefMap()}
wMap.set('child', temp.child);
wMap.has('child'); // true
delete referencer.child
wMap.has('child'); //false     

I creating kind of a tree structure that holds track of references that are still used in the current scope.

I will do a lot of merging, and recursively cleaning up a deeply nested structure can be very inefficient for this use case.


Solution

  • You can solve it by using WeakRef and FinalizationRegistry.

    Here is an example written in TypeScript:

    class InvertedWeakMap<K extends string | symbol, V extends object> {
      _map = new Map<K, WeakRef<V>>()
      _registry: FinalizationRegistry<K>
    
      constructor() {
        this._registry = new FinalizationRegistry<K>((key) => {
          this._map.delete(key)
        })
      }
    
      set(key: K, value: V) {
        this._map.set(key, new WeakRef(value))
        this._registry.register(value, key)
      }
    
      get(key: K): V | undefined {
        const ref = this._map.get(key)
        if (ref) {
          return ref.deref()
        }
      }
    
      has(key: K): boolean {
        return this._map.has(key) && this.get(key) !== undefined
      }
    }
    
    async function main() {
      const map = new InvertedWeakMap()
      let data = { hello: "world!" } as any
      map.set("string!", data)
    
      console.log('---before---')
      console.log(map.get("string!"))
      console.log(map.has("string!"))
    
      data = null
      await new Promise((resolve) => setTimeout(resolve, 0))
      global.gc() // call gc manually
    
      console.log('---after---')
      console.log(map.get("string!"))
      console.log(map.has("string!"))
    }
    
    main()
    

    It must be run with the --expose-gc option in the node.js environment.