9

This article describe getters. It has a section " Smart / self-overwriting / lazy getters" And it's unclear for me, are getters 'memoized' by default or should I implement this feature by myself

e.g.

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

or can I just write:

class Foo() {
  get boo() {
    return new Boo();  
  }
}

to have the same result?

kharandziuk
  • 12,020
  • 17
  • 63
  • 121

5 Answers5

7

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
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • Actually, [lazy getters don't work in classes](https://stackoverflow.com/questions/37977946/lazy-getter-doesnt-work-in-classes) – vrugtehagel Dec 10 '20 at 18:43
  • @vrugtehagel: Quite right. I so rarely use classes in JS, that I never did realize. Updated with a technique that does work. Thanks! – Scott Sauyet Dec 10 '20 at 19:00
5

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.

Joe Clay
  • 33,401
  • 4
  • 85
  • 85
4

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

Eric Kim
  • 10,617
  • 4
  • 29
  • 31
junvar
  • 11,151
  • 2
  • 30
  • 46
0

consider this code:

class Person {
    static get SHORT() { return 0; }//rvalue
}

versus

class Person {}
Person.SHORT = 0;//lvalue

Although both return the same result, the latter is actually faster (because it avoids the function call overhead); though the js engine can make optimizations that nullify one over the other.

Rafael
  • 7,605
  • 13
  • 31
  • 46
0

You can do it all on one line:

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

This is easy to remember and isn't convoluted. Got to consider the maintenance factor, and try to keep things simple.

toddmo
  • 20,682
  • 14
  • 97
  • 107