1

Is there any point in repeating this pattern for every property in JavaScript?

class Thing {
  get myProp() {
    return this._myProp;
  }
  set myProp(value) {
    this._myProp = value;
  }
}

I understand that getters/setters can be useful if you're doing additional work in the methods, but recreating the basic functionality here seems like needless repetition. If I instantiate, I can still manipulate the backing property (._myProp) directly, so I feel like I could just as easily leave these out and perform my assignment and access in the more typical, ad-hoc fashion.

I suppose you could argue that defining the interface this way (with the underscore-prefixed property name) signals to users that it's not meant to manipulate the property, but that seems like a flimsy reason for potentially dozens of these.

diplosaurus
  • 2,538
  • 5
  • 25
  • 53
  • It helps in controlling access to that property. Whether you need that or not depends but if you do, you want to have it early. If you're using getters and setters, you can easily do more stuff - an example is reacting to change (observable). You can make a property read only by not having a setter. You can have a "property" that's entirely computed - say, `getFullname` returns you `firstName + lastName` and `setFullname` splits and sets both names. Some of the applications can be done in other ways, so, again, whether you need it or not depends. – VLAZ Sep 18 '18 at 19:52
  • 1
    @vlaz "*you want to have it early*" - [YAGNI](https://en.wikipedia.org/wiki/YAGNI) – Bergi Sep 18 '18 at 20:03
  • Again - it depends. I've been on the receiving end of that. Namely, we had a bunch of objects where values were being displayed. Back in the early days of the project somebody did go YAGNI and all values were used as is, then *maybe* some got translated, *maybe* not. Sometimes only the translated value was kept by the object and the original was discarded. It caused massive problems when everything had to be translated *and* you also needed the original values in some cases. It was a massive pain reworking everything to obey (sort of) get/set instead just directly using properties. – VLAZ Sep 18 '18 at 20:10
  • @vlaz I don't understand. The cool thing about accessor properties in JS is that they don't differ from data properties in their usage, so you don't need to change the consuming code. I understand that changes in the interface (especially exposing both translated and untranslated values) are cumbersome, but I would not attribute the problems to getters/setters. – Bergi Sep 18 '18 at 20:19
  • The problem is that in some instances the value *from the server* (untranslated) was used for two different purposes - display (translated) and operations (e.g., check if an item has similar values as another item). That can fall apart when the two values aren't the same - one is translated another isn't. This requires adding more properties and then matching the correct ones. Instead of just having a `setX` and handling it from there. An object with, say, 6 display properties ends up with 12 properties *as a start*, otherwise. – VLAZ Sep 18 '18 at 20:23

4 Answers4

4

In compiled languages, it's common for people to do this. This is because in those languages, assigning to a field and invoking a setter may be identical to the programmer, but they compile to two completely different operations.

If I wanted to add a side effect for setting a field in a C# class, for example, and that field was being set directly instead of through a setter? Changing it to a setter would cause some issues. I wouldn't have to rewrite any of the consuming code, but I would have to recompile all of it. This is, of course, a huge problem if your public releases are compiled.

JavaScript is subject to no such considerations, though, so making everything into a getter/setter prematurely is kind of silly. If you see someone doing this, more than likely you're dealing with someone who learned the convention from another language and carried it into JavaScript, without thinking a whole lot about why.

sripberger
  • 1,682
  • 1
  • 10
  • 21
  • 3
    Can't agree more. Adopting habits without grasping the reasoning behind it is the root of all evil (as well as premature optimizations :)) Related: https://security.stackexchange.com/a/33471 – Jonas Wilms Sep 18 '18 at 20:09
1

Using an accessor property in the fashion you describe (set and retrieve a "background" data property) is virtually semantically identical to using a data property directly. There are some differences: the accessor property will exist on instance's prototype, rather than on the instance directly (though the instance will have the "background" data property), but this won't really affect anything unless you are doing advanced introspection on your class instances.

The only advantage is ease of modifying the code if you want to introduce more sophisticated accessor behavior in the future. If you forsee a need to add accessor behavior, use this pattern to save yourself time in the future.

apsillers
  • 112,806
  • 17
  • 235
  • 239
0

Property accessors are useful to provide side effects or change original behaviour:

class Thing {
  get myProp() {
    console.log('myProp was read');
    return this._myProp;
  }
  set myProp(value) {
    if (!value)
      throw new Error('myProp cannot be falsy');
    this._myProp = value;
  }
}

There is no point in myProp getter/setter pure abstraction:

class Thing {
  get myProp() {
    return this._myProp;
  }
  set myProp(value) {
    this._myProp = value;
  }
}
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
-1

If I instantiate, I can still manipulate the backing property (._myProp) directly,

If private states are what you are looking for you can still use a weak map.

(function(scope) {
  "use strict";
  const prop = new WeakMap();

  scope.Foo = class {
    constructor() {
      prop.set(this, {});
      Object.seal(this);
    }
    get bar() {
      return prop.get(this)._bar;
    }
    set bar(value) {
      return prop.get(this)._bar = value;
    }
  }


}(this))

const f = new Foo;

f.bar = "bar";
f._bar = "_bar";
console.log(f.bar);
console.log(f._bar);

get and setters are also useful when implementing MVC, you can trigger events on property change.

(function(scope) {
  "use strict";
  const prop = new WeakMap();

  scope.Foo = class {
    constructor() {
      prop.set(this, {});
      prop.get(this)._target = new EventTarget
      Object.seal(this);
    }
    get bar() {
      return prop.get(this)._bar;
    }
    set bar(value) {
      prop.get(this)._bar = value;
      prop.get(this)._target.dispatchEvent(new CustomEvent('change', {
        detail: value
      }));
    }
    addEventListener(event, listener) {
      prop.get(this)._target.addEventListener(event, listener)
    }
  }


}(this))

const f = new Foo;
f.addEventListener('change', function(event) {
  console.log("changed!", event.detail);
});
f.bar = "bar";
mpm
  • 20,148
  • 7
  • 50
  • 55