Skip to content

Instantly share code, notes, and snippets.

@HunterKohler
Last active July 4, 2021 11:18
Show Gist options
  • Save HunterKohler/ee7cfd3a49c1343ab540959d61887733 to your computer and use it in GitHub Desktop.
Save HunterKohler/ee7cfd3a49c1343ab540959d61887733 to your computer and use it in GitHub Desktop.
Simple memoizing function and weak value map
const memoize = (() => {
const wvm = new WeakValueMap();
return function (func, hash = JSON.stringify) {
return function () {
const key = hash.apply(this, arguments);
if (wvm.has(key)) {
return wvm.get(key);
}
const ret = func.apply(this, arguments);
map.set(key, ret);
return ret;
};
};
})();
function isObject(value) {
return (
(typeof value == "object" && value !== null) ||
typeof value == "function"
);
}
function isNullish(value) {
return typeof value == "undefined" || value === null;
}
function defineInstanceProperty(cls, key, value) {
return Reflect.defineProperty(cls.prototype, key, {
value,
configurable: true,
enumerable: false,
writable: typeof value == "function",
});
}
class WeakValueMapIterator {
static #_ = (() => {
defineInstanceProperty(
this,
Symbol.toStringTag,
"WeakValueMap Iterator"
);
})();
#base;
#iter;
#type; // keys, values, or entries
#done = false;
/**
* @param {Map} base
* @param {"keys"|"values"|"entries"} [type="entries"]
*/
constructor(base, type = "entries") {
this.#type = type;
this.#base = base;
this.#iter = base[Symbol.iterator]();
}
next() {
let _next;
do {
if (_next) {
this.#base.delete(iter.value[0]);
}
_next = this.#iter.next();
} while (
_next.value?.[1] instanceof WeakRef &&
_next.value?.[1].deref === undefined
);
this.#done = _next.done;
if (this.#type == "keys") {
_next.value = _next.value?.[0];
} else if (this.#type == "values") {
_next.value = _next.value?.[1];
}
return _next;
}
get done() {
return this.#done;
}
[Symbol.iterator]() {
return this;
}
}
class WeakValueMap {
static Iterator = WeakValueMapIterator;
static #_ = (() => {
defineInstanceProperty(this, Symbol.toStringTag, "WeakValueMap");
defineInstanceProperty(this, Symbol.iterator, this.prototype.entries);
})();
#base = new Map();
constructor(entries) {
if (!isNullish(entries)) {
if (!isObject(entries)) {
throw new TypeError(
typeof entries +
" is not iterable (cannot read property " +
String(Symbol.iterator) +
")"
);
}
for (const entry of entries) {
if (!isObject(entry)) {
throw new TypeError(
`Iterator value ${entry} is not an entry object`
);
}
this.set(entry[0], entry[1]);
}
}
}
get(key) {
let value = this.#base.get(key);
if (value instanceof WeakRef) {
value = value.deref();
if (value === undefined) {
this.#base.delete(key);
}
}
return value;
}
set(key, value) {
this.#base.set(key, isObject(value) ? new WeakRef(value) : value);
return this;
}
has(key) {
if (!this.#base.has(key)) {
return false;
}
let value = this.#base.get(key);
if (value instanceof WeakRef) {
value = value.deref();
if (value === undefined) {
this.#base.delete(key);
return false;
}
}
return true;
}
delete(key) {
return this.has(key) && this.#base.delete(key);
}
clear() {
this.#base.clear();
}
entries() {
return new WeakValueMapIterator(this.#base);
}
forEach(callbackfn, thisArg) {
for (const [key, value] of this) {
callbackfn.call(thisArg, value, key, this);
}
}
keys() {
return new WeakValueMapIterator(this.#base, "keys");
}
values() {
return new WeakValueMapIterator(this.#base, "values");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment