javascriptclassgetsetkeypaths

Using 'this' in a getter/setter defined on a class field property. How to define class field getters/setters on child properties?


The issue in question relates to human readability and data organization. I would like to assign getters and setters as children of class field properties, however I can't get this to reference the class prototype data. I think this is the same problem solved by using self in a class constructor, but applied to class fields instead. Is there a comparable solution for class fields that would keep these definitions out of the constructor?

This is the format I would like to achieve. The constructor is not invoked because external data is not required for these field definitions. This throws a recursion error.

class data_controller {
  // class fields
  id = 12;
  a = 10;
  b = 20;
  // log data entries
  log = {
    entry: {
      get a() {
        return {
          id: this.id,
          value: this.a,
          label: 'a',
        };
      },
      get b() {
        return {
          id: this.id,
          value: this.b,
          label: 'b',
        };
      },
    },
  };
  // update data
  update = {
    set a(value) {
      this.a = value;
    },
    set b(value) {
      this.b = value;
    },
  };
}
// class usage
const data = new data_controller();
console.log(data.log.entry.a);
console.log(data.log.entry.b);
data.update.a = 50;
data.update.b = 60;
console.log(data.log.entry.a);
console.log(data.log.entry.b);

This is the format using 'self' and the constructor. I would like to keep the constructor free of these definitions.

class data_controller {
  // class fields
  id = 12;
  a = 10;
  b = 20;
  log = {};
  update = {};
  constructor() {
    self = this;
    // log data entries
    this.log.entry = {
      get a() {
        return {
          id: self.id,
          value: self.a,
          label: 'a',
        };
      },
      get b() {
        return {
          id: self.id,
          value: self.b,
          label: 'b',
        };
      },
    };
    // update data
    this.update = {
      set a(value) {
        self.a = value;
      },
      set b(value) {
        self.b = value;
      },
    };
  }
}
// class usage
const data = new data_controller();
console.log(data.log.entry.a);
console.log(data.log.entry.b);
data.update.a = 50;
data.update.b = 60;
console.log(data.log.entry.a);
console.log(data.log.entry.b);

This is a format which keeps the constructor free of definitions but does not implement the getter/setter class usage.

class data_controller {
  // class fields
  id = 12;
  a = 10;
  b = 20;
  // log data entries
  log = {
    entry: {
      a: () => {
        return {
          id: this.id,
          value: this.a,
          label: 'a',
        };
      },
      b: () => {
        return {
          id: this.id,
          value: this.b,
          label: 'b',
        };
      },
    },
  };
  // update data
  update = {
    a: (value) => {
      this.a = value;
    },
    b: (value) => {
      this.b = value;
    },
  };
}
// class usage
const data = new data_controller();
console.log(data.log.entry.a());
console.log(data.log.entry.b());
data.update.a(50);
data.update.b(60);
console.log(data.log.entry.a());
console.log(data.log.entry.b());

I believe I could also solve this issue by making the class field organization flat, but I would like to maintain multi-level structures.


Solution

  • You can add a reference to the containing object in the nested objects. It's parent in the code below. Then you can use this.parent to go up a level.

    class data_controller {
      // class fields
      id = 12;
      a = 10;
      b = 20;
      // log data entries
      log = {
        entry: {
          parent: this,
          get a() {
            return {
              id: this.parent.id,
              value: this.parent.a,
              label: 'a',
            };
          },
          get b() {
            return {
              id: this.parent.id,
              value: this.parent.b,
              label: 'b',
            };
          },
        },
      };
      // update data
      update = {
        parent: this,
        set a(value) {
          this.parent.a = value;
        },
        set b(value) {
          this.parent.b = value;
        },
      };
    }
    // class usage
    const data = new data_controller();
    console.log(data.log.entry.a);
    console.log(data.log.entry.b);
    data.update.a = 50;
    data.update.b = 60;
    console.log(data.log.entry.a);
    console.log(data.log.entry.b);