javascript and memoized getters

You're free to add memoization, e.g.

Non memoized,

class NonMemoized {
  constructor(prefix) {
    this.prefix = prefix;
  }

  get myFunc() {
    return this.prefix + Math.random().toString();
  }
}

let nonMemoized = new NonMemoized('new number each time ');
console.log(nonMemoized.myFunc);
console.log(nonMemoized.myFunc);

Memoized, nice for when u want to create an object once and always return the same object (but don't want to create in the constructor because maybe it's not necessary all the time or some other reason)

class MemoizedManually {
  constructor(prefix) {
    this.prefix = prefix;
  }

  get myFunc() {
    return this._myFunc_ = this._myFunc_ || this.prefix + Math.random().toString();
  }
}

let memoizedManually = new MemoizedManually('same number ');
console.log(memoizedManually.myFunc);
console.log(memoizedManually.myFunc);

Lastly, if you have a bunch of functions you want to memoize but don't want to repeat that this.x = this.x || something computation in each function (which you really shoudln't repeat, as it's not really the job of myFunc to memoize itself:

class Memoized {
  constructor(prefix) {
    this.prefix = prefix;
  }

  get myFunc() {
    return this.prefix + Math.random().toString();
  }
}

const memoizeGetter = (clazz, functionName) => {
  let func = Object.getOwnPropertyDescriptor(clazz.prototype, functionName);
  let cacheKey = `_${functionName}-cache_`;
  Object.defineProperty(clazz.prototype, functionName, {
    get: function () {
      return this[cacheKey] = this[cacheKey] || func.get.call(this);
    }
  });
};

memoizeGetter(Memoized, 'myFunc');

let memoized = new Memoized('also same number ');
console.log(memoized.myFunc);
console.log(memoized.myFunc);

Nice thing about getters is they don't take arguments, so you don't have to worry about ...args, but do need to worry about binding this


No, there is no language-level support for memoized getters in JavaScript. In your second example, a new object would be created every time boo was accessed.


The most interesting bit of that article was Smart / self-overwriting / lazy getters, which offers this technique:


class Foo {
  get boo() {
    delete this.boo;
    return this.boo = new Boo();
  }
}

With this your Foo objects don't go through the hassle of creating their boo properties until you ask for it. Then it's created once and further requests for it simply return the same object. This makes sense if new Boo() is in someway resource-intensive to create and reasonably often is not needed.

Theoretically, you could extend this to allow you to delete the current version and recreate it on next access. But that's a lot more code, and is probably a fairly rare need.

Update

A comment from vrugtehagel correctly pointed out that the above technique, while fine for plain objects, does not work for classes.

Here's a variant which does work:

class Boo {
  static counter = 0
  constructor () {
    this.x = ++Boo.counter
    console .log (`creating Boo(${this.x})`)
  }
}

class Foo {
  get boo () {
    Object .defineProperty (
      this, 
      "boo", 
      { value: new Boo(), writable: false}
    )
    return this .boo;
  }
}

const f = new Foo()

console .log (f.boo) 
console .log (f.boo) // no 'creating Boo' log, Boo constructor only created once