是否可以在 JavaScript 中实现双向弱映射?

时间:2021-07-26 22:11:37

标签: javascript typescript weak-references weakmap

是否有可能制作像 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) 的第二个参数必须允许是任何东西,而不仅仅是对象。例如,v 可以是 number

2 个答案:

答案 0 :(得分: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 );

答案 1 :(得分:0)

看起来不应该比

更复杂
device_count

注意事项

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

  • 如果 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); } } k 是一个对象,由于 vSetMap 和 {{1}使用引用相等,只有完全相同的对象才会匹配。其他任何东西,即使是完全重复的也不会匹配。

相关问题