6

Imagine I have 3 classes Child, Parent and Grandparent connected in hierarchy as follows:

class Grandparent {
  set myField(value) {
    console.log('Grandparent setter');
  }
}

class Parent extends Grandparent {
  set myField(value) {
    console.log('Parent setter');
  }
}

class Child extends Parent {
  set myField(value) {
    //I know how to call Parent's setter of myField:
    //super.myField = value;
    //But how to call Grandparent's setter of myField here?
  }
}

How can I call Grandparent's setter of myField in setter of Child class?

I'm interested particularly in setter, not a method. Also it's much preferable to not make changes in Parent of Grandparent classes.

I don't see how that is possible using super because it references just Parent class, as well as using something like Grandparent.prototype.<what?>.call(this, ...) because I don't know what exactly to call in the prototype.

Does anyone have any suggestions for this case?

Thanks in advance!

Alena Levina
  • 103
  • 1
  • 8
  • 1
    without a distinct ref to it, you can't call it. – dandavis Mar 18 '19 at 19:31
  • 1
    Possible duplicate of [How can I call my class' parent's parent's constructor in ES6?](https://stackoverflow.com/questions/42861855/how-can-i-call-my-class-parents-parents-constructor-in-es6) – Get Off My Lawn Mar 18 '19 at 19:32
  • https://stackoverflow.com/questions/36370790/javascript-call-super-on-parents-parent – Get Off My Lawn Mar 18 '19 at 19:32
  • @GetOffMyLawn thanks for ideas, but my questions is particularly about setter, there is a specific for it which differs it from how same can be accomplished for method – Alena Levina Mar 18 '19 at 23:51

4 Answers4

8

using something like Grandparent.prototype.<what?>.call(this, ...)

You're on the right track there, you can access the setter method using Object.getOwnPropertyDescriptor:

Object.getOwnPropertyDescriptor(Grandparent.prototype, "myField").set.call(this, value);

There is a much easier way though: using the Reflect.set helper with a custom receiver:

Reflect.set(Grandparent.prototype, "myField", value, this);

This also has the advantage that it still works when Grandparent doesn't define a setter.


That said, I agree with @Dinu that there's probably a problem with your class hierarchy (or your general design, maybe you shouldn't even use classes or inheritance) when you need to do this.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Beat me to the answer... I was so focused on writing the helper function for recursive [`Object.getPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) that I didn't notice this! – Patrick Roberts Mar 18 '19 at 19:45
  • Prefered first option with Object.getOwnPropertyDescriptor since Reflect.set is not supported in IE. Thank you! – Alena Levina Mar 18 '19 at 23:55
2

You can use Reflect.set() with the optional receiver argument like this:

class Grandparent {
  set myField(value) {
    console.log('Grandparent setter');
  }
}

class Parent extends Grandparent {
  set myField(value) {
    console.log('Parent setter');
  }
}

class Child extends Parent {
  set myField(value) {
    Reflect.set(Grandparent.prototype, 'myField', value, this);
  }
}

new Child().myField = 'foo';

If you don't have an explicit reference to Grandparent.prototype, you can use Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(this))) instead which is obviously a lot less preferable. You could create a helper function though:

function getPrototypeOf (target, level = 1) {
  return level > 0 ? getPrototypeOf(Object.getPrototypeOf(target), level - 1) : target;
}

and use getPrototypeOf(this, 3) in place of Grandparent.prototype to recurse up the prototype chain to the grandparent:

function getPrototypeOf (target, level = 1) {
  return level > 0 ? getPrototypeOf(Object.getPrototypeOf(target), level - 1) : target;
}

class Grandparent {
  set myField(value) {
    console.log('Grandparent setter');
  }
}

class Parent extends Grandparent {
  set myField(value) {
    console.log('Parent setter');
  }
}

class Child extends Parent {
  set myField(value) {
    Reflect.set(getPrototypeOf(this, 3), 'myField', value, this);
  }
}

new Child().myField = 'foo';
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
1

You might find a way to do it, but you shouldn't, not while you're doing class-based OOP, because it breaks the class-based model. In any sane environment, if Parent implements setX(), it expects that setX() behaves the way it defined it, in its context. It does not override setX() so that it can behave the way it did before.

So, if you're asking this, you either designed your class hierarchy wrong, or you actually need the prototype-based OOP.

If you write Parent, I assume what you are trying to obtain is an unmediated access to a certain property. The way to go is to define a method on parent/gradpa, like setXClean(), that is used as a celan setter. In this context, you probably want it to be protected and final on the grandparent.

Dinu
  • 1,374
  • 8
  • 21
  • Idk why people give themselves a hard time, instead of creating a new instance of the class, super is useful, but supers super is way too messy. – user3531149 Mar 18 '19 at 19:39
  • 1
    This is useful but feels more like a comment than an answer. – Tyler Roper Mar 18 '19 at 19:39
  • It's hard to give an answer without having more info about the background of why she needs this. But to make it an answer, I tried to make a guess and edited with a suggestion to add a clean setter. I don't know though who is authoring every entity of the tree of classes, so making suggestions without the design background is not possible... But as it was formulated, it is certainly misdirected, which is what I would consider the answer, like asking "where can I find a giraffe that speaks Spanish"? – Dinu Mar 18 '19 at 19:41
  • Idea that the question I'm asking is the evidence of wrong design is a good point! Unfortunately I'm in the position where I need to avoid making changes in Parent and Grandparent classes, so for me the answer which only changes Child class is preferable – Alena Levina Mar 18 '19 at 23:44
  • Then probably the AOP style of doing things (see the others' answers using reflection) is the only way to go. That or extend Child directly from Grandparent and duplicate all Parent's code (except the unwanted setter) into Child... – Dinu Mar 18 '19 at 23:49
0

The easiest way in my opinion, would be to just create another unique setter or method in the Grandparent class that sets myField.

class Grandparent {
  set myField(value) {
    console.log('Grandparent setter');
  }
  set grandParent_myField(value) { this.myField = value }
}

class Parent extends Grandparent {
  set myField(value) {
    console.log('Parent setter');
  }
}

class Child extends Parent {
  set myField(value) {
    this.grandParent_myField = value
  }
}

Another option would be to put super.myField in each setters body:

class Grandparent {
  constructor() { this._myField = null }
  set myField(value) {
    this._myField = value
  }
}

class Parent extends Grandparent {
  set myField(value) {
    super.myField = value
  }
}

class Child extends Parent {
  set myField(value) {
    super.myField = value
  }
}
Get Off My Lawn
  • 34,175
  • 38
  • 176
  • 338