有可能在JavaScript中实现双向弱映射吗?



是否有可能使像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内部,那么o所指向的SomeClass对象可以被收集。

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

方法如下:

<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 );

FinalizationRegistry使它更完美!

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

它必须在node.js环境中使用--expose-gc选项运行。

似乎不应该比

更复杂
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,WeakSetWeakMap的方法使用引用相等性,因此只有完全相同的对象将匹配。其他任何内容,即使是完全重复也不会匹配。

相关内容

  • 没有找到相关文章

最新更新