Memoization with Lazy Getters

snippet

TIL JavaScript getters can simply be deleted, even if there’s no corresponding setter:

let obj = {
    data: 123,
    get hash() {
        return this.data * Math.random();
    }
};

obj.hash = 666; // 💥 TypeError: setting getter-only property "hash"

delete obj.hash;
obj.hash = 888; // ✅

We can use this to avoid redundant computations, replacing our getter with the respective value upon first invocation:

let obj = {
    data: 123,
    get hash() {
        let value = this.data * Math.random();
        delete this.hash;
        this.hash = value;
        return value;
    }
};

We could alternatively employ Object.defineProperty to the same effect:

let obj = {
    data: 123,
    get hash() {
        let value = this.data * Math.random();
        Object.defineProperty(this, "hash", {
            value,
            writable: false
        });
        return value;
    }
};

This might be preferable because it doesn’t (temporarily) change our object’s shape; delete can trip up engines’ performance optimizations – though I haven’t researched this particular scenario.

Note that for some scenarios, we might need to distinguish between instance and prototype properties. That also opens up an intriguing opportunity:

class Record {
    set data() {
        this._data = data;
        delete this.hash; // resets memoization
    }

    get hash() {
        let value = this._data * Math.random();
        Object.defineProperty(this, "hash", {
            value,
            configurable: true, // enables reset
        });
        return value;
    }
}

Here we’re caching our value in an instance property, which takes precedence over the prototype’s getter. We can later delete that instance property so that hash is recalculated via the prototype when accessed again.