0

是否可以制作类似 WeakMap 的两种方式(通过其键获取值,或通过其值获取键)?

用法如下所示(在 TypeScript 语法中更好地说明):

class TwoWayWeakMap {
  // What goes here?
}

class SomeClass {}

const map = new TwoWayWeakMap<SomeClass, number>()

const o = new SomeClass

map.set(o, 42)

console.log(map.get(o)) // logs "42"
console.log(map.keyFrom(42)) // logs "SomeClass {}" (the `o` object)

在以后的任何时候,如果o除了在内部之外不再被引用TwoWayWeakMap,那么可以收集指向的SomeClass对象。o

笔记!map.set(k, v)必须允许 to的第二个参数是任何东西,而不仅仅是对象。v例如,可以是一个number

4

2 回答 2

1

这是一种方法:

<script type=module>
    let tick = 0
  
    const loop = setInterval(() => {
        const obj = window.map.keyFrom(42)
    
        console.log(`o still exists? (${tick++})`, !!obj)
    
        if (!obj) {
            clearInterval(loop)
            console.log('o was collected!')
        }
    }, 300)
</script>

<script type=module>
    class TwoWayWeakMap /*<K extends object = object, V = unknown>*/
      extends WeakMap /*<K, V>*/ {
    
        #refs /*: Set<WeakRef>*/ = new Set();
    
        constructor() {
            super();
            setInterval(() => this.maybeCleanup(), 1000);
        }
    
        set(k /*: K*/ , v /*: V*/ ) /*: void*/ {
            super.set(k, v);
            this.#refs.add(new WeakRef(k));
        }
    
        keyFrom(v /*: V*/ ) /*: K | undefined*/ {
            for (const ref of this.#refs) {
                const o = ref.deref();
                if (!o) {
                    this.#refs.delete(ref);
                    continue;
                }
                if (this.get(o) === v) return o;
            }
        }
    
        maybeCleanup() {
            for (const ref of this.#refs) {
                const o = ref.deref();
                if (!o) this.#refs.delete(ref);
            }
        }
    }
  
    class SomeClass {}

    function main() {
        const map = (window.map = new TwoWayWeakMap /*<SomeClass, number>*/());
        const o = new SomeClass();
        map.set(o, 42);
        console.log(map.get(o)); // logs "42"
        console.log('Get object from key:', !!map.keyFrom(42)); // logs "true"
    }
  
    main();
  
    // At this point there is no reference to `o`, except by
    // WeakRef and WeakMap, so `o` should be collectable.
</script>

Kaiido 提供了另一种方法来做到这一点,使用第二张地图来消除迭代的需要:

class TwoWayWeakMap extends WeakMap {
  #reverseMap;
  constructor( iterable ) {
    super(iterable);
    this.#reverseMap = new Map();
    if (iterable) {
      for (const [k,v] of iterable ) {
        this.set(k,v);
      }
    }
  }
  set(k,v) {
    super.set(k,v);
    this.#reverseMap.set(v, new WeakRef(k));
  }
  keyFrom(v) {
    const k = this.#reverseMap.get(v)?.deref();
    if (!k) { // suboptimal clean value at getting...
      this.#reverseMap.delete(v);
    };
    return k;
  }
}

class SomeClass {}

const map = new TwoWayWeakMap();
{
  const o = new SomeClass();

  map.set(o, 42)

  console.log(map.get(o)) // logs "42"
  console.log(map.keyFrom(42) === o) // logs "SomeClass {}" (the `o` object)
}
// check it gets collected, eventually
// convert to Boolean to avoid the console keeping an hard reference
setInterval(() => console.log(!!map.keyFrom(42)), 1000 );

于 2021-07-28T06:09:23.907 回答
0

看起来它不应该比它更复杂

class TwoWayWeakMap extends WeakMap {
  
  constructor(iterable) {
    super(iterable);
    if (iterable) {
      for (const [k,v] of iterable ) {
        this.set(k,v);
      }
    }
  }

  set(k,v) {
    super.set(k,v);
    super.set(v,k);
  }

}

注意事项

  • 这里有一个隐含的假设,即键和值的集合并集本身形成一个集合(即键集本身是唯一的,值集本身是唯一的,并且两者之间存在共性)。

  • 如果其中一个kv是一个对象,因为Set, Map, WeakSet, 和的方法WeakMap使用引用相等,所以只有完全相同的对象会匹配。其他任何东西,即使它是完全重复的,也不会匹配。

于 2021-07-26T22:32:13.563 回答