Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call setter of grandparent class in Javascript

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!

like image 964
Alena Levina Avatar asked Mar 18 '19 19:03

Alena Levina


3 Answers

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.

like image 126
Bergi Avatar answered Nov 03 '22 16:11

Bergi


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';
like image 40
Patrick Roberts Avatar answered Nov 03 '22 16:11

Patrick Roberts


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.

like image 22
Dinu Avatar answered Nov 03 '22 16:11

Dinu