Search code examples
javascriptclassgetter-setter

Good solution for multiple identical getter/setter functions in ES5/6 classes?


I'm looking for an elegant way to declare identical getter/setters for multiple properties in ES6 classes.

Rather than having a slew of:

  set prop_1(value){
      this.prop_1 = value;
      console.log(`set prop_1 = ${value} and calling updateroom`);
      db.updateRoom(this, 'prop_1');
  }

...

  set prop_n(value){
      this.prop_n = value;
      console.log(`set prop1 = ${value} and calling updateroom`);
      db.updateRoom(this, 'prop_n');
  }

I'd like to do something a little more maintainable like this in the class definition adjacent to the other getters and setters:

['prop_1', 'prop_2' ... 'prop_n'].forEach(prop => {
      set [prop](value) {
        this[prop] = value;
        console.log(`set ${prop} = ${value} and calling updateroom`);
        db.updateRoom(this, prop);
      }
});

But of course can't do that inside the class definition as literals aren't one of the things syntactically allowed there.

Can't even add the setters to the class definition after declaration later via e.g.:

class Room {
// stuff
}

['initialised', 'type', 'owner'].forEach(prop => {
    Object.defineProperty(Room, prop, {
      set: function(value) {
        this[prop] = value;
        console.log(`set ${prop} = ${value} and calling updateroom`)
        db.updateRoom(this, prop);
      }
})

as there is no instance at that point.

So end up going down an arcane path of decorating the constructor, which just means anyone trying to figure out what the heck I was trying to achieve afterwards ends up with a half hour headache and way more complexity.

Am I missing something, has anyone figured out an elegant way of coding this efficiently without repetition of the getter-setters?


Solution

  • I'd like to do something a little more maintainable like this in the class definition adjacent to the other getters and setters:

    ['prop_1', 'prop_2' ... 'prop_n'].forEach(prop => {
          set [prop](value) {
            this[prop] = value;
            console.log(`set ${prop} = ${value} and calling updateroom`);
            db.updateRoom(this, prop);
          }
    });
    

    and

    Can't even add the setters to the class definition after declaration later via e.g.:...as there is no instance at that point.

    Right, but you can use Object.defineProperty to do it, setting the properties on the object that will be the prototype of those instances (Room.prototype). After the class declaration:

    class Room {
        // ...
    }
    

    ...you can add those setters to Room.prototype:

    ['prop_1', 'prop_2'/* ... 'prop_n'*/].forEach(prop => {
        Object.defineProperty(Room.prototype, prop, {
            set: function(value) {
                // ...save the value somewhere (*NOT* `this[prop] = value;`,
                // which will call the setter again, resulting in a stack
                // overflow error...
            }
        });
    });
    

    Remember that class notation is mostly syntactic sugar on prototypical inheritance (but, you know, the good kind of sugar). You still have a Room.prototype object, and it's perfectly valid to add things to it outside the class declaration.

    Live Example (in this example, I just store the values on a separate values property object):

    class Room {
        constructor() {
           this.values = {};
        }
    }
    ['prop_1', 'prop_2', 'prop_n'].forEach(prop => {
        Object.defineProperty(Room.prototype, prop, {
            set: function(value) {
                console.log(`set ${prop} = ${value}...`);
                this.values[prop] = value;
            },
            get: function() {
                return this.values[prop];
            }
        });
    });
    
    const r = new Room();
    r.prop_1 = 42;
    console.log("r.prop_1 = ", r.prop_1);
    r.prop_2 = "Answer";
    console.log("r.prop_2 = ", r.prop_2);