2

I'm trying to programmatically add and delete (for caching purposes) getters from an object. I'm adding a getter like this:

Object.defineProperty(obj, 'text', {
  get: getter
})

obj.text should only be evaluated the first time it's accessed, and the calculated value cached for subsequent calls.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get shows how to implement a smart getter like this:

get notifier() {
  delete this.notifier;
  return this.notifier = document.getElementById('bookmarked-notification-anchor');
}

I can't use delete this.text inside my getter function however. What I've found is, that this is the prototype of the Object rather than the instance - is that correct? And if so, how can I delete the getter of the instance and replace it with the calculated value?

edit:

As per the comments, the getter and object looks something like this:

var obj = {}
obj.value = '2018-04-21T12:00:00Z000'

Object.defineProperty(obj, 'text', {
  get: function () {
    delete this.text  // doesn't seem to work

    if (this.value == null) return ''
    var text = this.value.split('T')[0].split('-').reverse().join('.')
    this.text = text
    return text // return this.text ends in Maximum call stack size exceeded
  }
})
Johannes
  • 1,249
  • 3
  • 17
  • 33
  • It's not clear what you're asking. Which form are you using? Are you doing this on the prototype of something, or on the object itself? *Neither* form should lead you to think `this` will be the prototype of an object rather than the object itself, unless you're calling it oddly. – T.J. Crowder Apr 24 '18 at 15:11
  • Inside your getter `this` does refer to your object and not its prototype. Maybe the best way is not to delete anything - but simply use a private variable to store the result and return the result once it is set. Something like `void function(){ var a; return { get(){ return a || (a = myCalculation()); }}}()`. This way you do lose the benefit of a `getter` in the first place, which is that it doesn't store a reference to your HTMLElement, meaning that if it gets deleted it can actually be removed as no reference to it is stored. – somethinghere Apr 24 '18 at 15:12
  • If you want to remove the getter and replace it with a value, you have to redefine the property, i.e. you have to call `Object.defineProperty(obj, propertyName, { value: newValue })` – Máté Safranka Apr 24 '18 at 15:16
  • @T.J.Crowder you are right, the issue I was refering to was using classes. – Johannes Apr 24 '18 at 15:18
  • Why don't you simply return that value in getter method `get notifier() { return document.getElementById('bookmarked-notification-anchor'); }` – Zohaib Ijaz Apr 24 '18 at 15:22

2 Answers2

6

You need to make the property configurable so that you can delete it:

var obj = {value: '2018-04-21T12:00:00Z000'};

Object.defineProperty(obj, 'text', {
  get: function () {
    delete this.text

    if (this.value == null) return ''
    var text = this.value.split('T')[0].split('-').reverse().join('.')
    console.log("updating")
    this.text = text
    return text
  },
  configurable: true
//^^^^^^^^^^^^^^^^^^
});
console.log("first access");
console.log(obj.text);
console.log("second access");
console.log(obj.text);

Apart from that, if you are having issues with properties inherited from a prototype object, you cannot delete it but need to use defineProperty to shadow it.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Quite so, that lets you use simple assignment -- after deleting it. :-) – T.J. Crowder Apr 24 '18 at 15:24
  • Yes, this does work with my use-case! I'm curious though, since `delete this.text` works perfectly if the object specifies the getter itself (without using `defineProperty`). The docs say the property defaults to `false`, but is it implicitly set or what's happening here? – Johannes Apr 24 '18 at 15:27
  • 2
    @Johannes: If you mean `var obj = {get notifier() { ... }};`, that accessor is defined with `configurable: true`: https://tc39.github.io/ecma262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation. The defaults are specifically for `defineProperty`/`defineProperties` (and for other places in the spec where they aren't specifically mentioned). – T.J. Crowder Apr 24 '18 at 15:32
2

What I've found is, that this is the prototype of the Object rather than the instance...

Not in the code you've shown, not unless you're calling it oddly.

One way to do it is to use Object.defineProperty to redefine the property.

So for instance, if you're doing this on a one-off object:

var obj = {
  get notifier() {
    var value = Math.random();
    console.log("Getter called");
    Object.defineProperty(this, "notifier", {
      value: value
    });
    return value;
  }
};
console.log("First use");
console.log(obj.notifier);
console.log("Second use");
console.log(obj.notifier);

Or if it's not a one-off:

function Maker() {
}
Object.defineProperty(Maker.prototype, "notifier", {
  get: function() {
    var value = Math.random();
    console.log("Getter called");
    Object.defineProperty(this, "notifier", {
      value: value
    });
    return value;
  },
  configurable: true
});
var obj = new Maker();
console.log("First use");
console.log(obj.notifier);
console.log("Second use");
console.log(obj.notifier);

I've stuck to ES5-level stuff above since you didn't seem to be using any ES2015+ features.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875