Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

WeakKeySet: a Set that holds its elements weakly

Mark S. Miller edited this page Feb 22, 2018 · 6 revisions

The following code implements the Set API, including enumeration, and thus makes visible the non-determinism of garbage collection.

In the following code, NULL stands for either null or undefined depending on https://github.com/tc39/proposal-weakrefs/issues/25

function makeDerefIterator(refIter, isPair = false) {
  return {
    next() {
      while (true) {
        const refResult = refIter.next();
        if (refResult.done) {
          return refResult;
        }
        let value = refResult.value.deref();
        if (value !== NULL) {
          if (isPair) {
            value = [value, value];
          }
          return { value, done: false };
        }
      }
    },
    [Symbol.toStringTag]: 'WeakKeySet Iterator'
  };
}

/**
 * Set API, but does not inherit from Set
 */
class WeakKeySet {
  #wm = undefined;
  #group = undefined;
  #refSet = undefined;

  #init() {
    this.#wm = new WeakMap();
    const refSet = new Set();
    this.#group = new WeakRefGroup(iter => {
      for (const wr of iter) {
        refSet.delete(wr);
      }
    });
    this.#refSet = refSet;
  }

  constructor(opt_iterable = undefined) {
    this.#init();
    if (opt_iterable) {
      for (const k of opt_iterable) {
        this.add(k);
      }
    }
  }

  static get [Symbol.species]() { return this; }
  has(k) { return this.#wm.has(k); }

  add(k) {
    if (!this.#wm.has(k)) {
      const wr = this.#group.makeRef(k, undefined);
      this.#wm.set(k, wr);
      this.#refSet.add(wr);
    }
    return this;
  }

  delete(k) {
    const wr = this.#wm.get(k);
    this.#wm.delete(k);
    return this.#refMap.delete(wr);
  }

  clear() {
    this.#group.shutdown();
    this.#refSet.clear();
    this.#init();
  }

  values() {
    const refIter = this.#refSet[Symbol.iterator]();
    return makeDerefIterator(refIter);
  }

  entries() {
    const refIter = this.#refSet[Symbol.iterator]();
    return makeDerefIterator(refIter, true);
  }

  forEach(callback, thisArg = undefined) {
    for (const e of this) {
      callback.call(thisArg, e, e, this);
    }
  }

  get size() {
    let count = 0;
    for (const e of this) {
      count++;
    }
    return count;
  }
}

WeakKeySet.prototype[Symbol.iterator] =
  WeakKeySet.prototype.keys =
  WeakKeySet.prototype.values;

WeakKeySet.prototype[Symbol.toStringTag] = 'WeakKeySet';
Clone this wiki locally