How to call setter of grandparent class in Javascript - 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!

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.

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';

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.

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
}
}

Related

Is there a more direct way to achieve this in Javascript?

I have an inheritance like this:
class Parent {
some_object_property = ["some_object_property_depending_on_initialization"];
constructor() {...}
}
class Child extends Parent {
some_object_property = [...super.some_object_property, 'child_property']
}
Is there a way to let the Child inherit and extend object property some_object_property from the Parent?
I know I can achieve the end result through getter and setter. Is there a more direct way?
Did you mean this?
class Parent {
constructor() {
this.someProp = ['someprop test', 'something else'];
}
}
class Child extends Parent {
constructor() {
super();
this.someProp = [...this.someProp ,'added child prop'];
}
}
let myChild = new Child();
console.log(myChild.someProp);
It produces an error saying "super is not an identifier recognizable".
Yes, you cannot use super in a class field initialiser. Either put that line inside the constructor where it belongs (like in #DannyEbbers' answer), or just use the this keyword instead - it's just as good, no need to use super here:
class Parent {
some_object_property = ["some_object_property_depending_on_initialization"];
}
class Child extends Parent {
some_object_property = [...this.some_object_property, 'child_property']
}
this.some_object_property will refer to the instance property that was initialised by the parent constructor before you're overwriting it with a new value - it runs as if written inside the constructor.

Javascript Child Class method not overriding Parent Class Method

I am trying to override one method from the parent class, but there are some issues.
Below is the code snippet of my scenario which I am trying.
class Parent {
add = () => {
console.log('Parent method');
}
}
class Child extends Parent {
add () {
console.log('Child Method');
}
}
// Creating an instance
const child = new Child();
child.add();
It is calling the Parent method add as that is arrow function, Can someone explain why this is happening. If I make the parent function a simple javascript method then child is able to override.
Additonal Details :
I don't have access to Parent as it is part of library.
I can't make my child class method as instance properties (arrow function)
, the reason for being that there are further
specification written for child (child of child) and If we use arrow
functions we will not be able to call the super.
Child function name can't be renamed.
This is one of few reasons why arrow methods aren't convenient. They limit the ways in which a class can be extended and tested.
Class fields (which arrow methods are) are syntactic sugar for constructor code:
class Parent {
constructor() {
this.add = () => {...};
}
}
Only another arrow method can override parent arrow method, because they are defined in class constructor, not on class prototype:
class Child extends Parent {
add = () => {
/* no super.add here because add is not prototype method */
}
}
If super.add is intended to be used, a workaround is to store parent method:
class Child extends Parent {
superAdd = this.add;
add = () => {
this.superAdd();
}
}
Notice that since this is syntactic sugar for constructor code, the order in which superAdd and add are defined matters.
The parent add is an instance property, and it overshadows the child's class method, which is part of the instance's prototype. It's a bit hacking, but you can rename and delete the class property in the constructor:
class Parent {
add = () => {
console.log('Parent method');
}
}
class Child extends Parent {
constructor() {
super();
this.parentAdd = this.add;
delete this.add;
}
add() {
console.log('Child Method');
this.parentAdd(); // if you need call the parent's method
}
}
const child = new Child();
child.add();

Javascript classes : how to access overridden parent class functions in parent class code

Javascript ES6 ( node 8.4.0 and latest chrome and recent Firefox )
I expected
class Parent {
init(){
console.log("Parent init") ;
this._surname = "McClass" ;
}
constructor() {
console.log("Parent constructor") ;
this.init();
}
get surname(){
return this._surname ;
}
}
class Child extends Parent {
init(){
console.log("Child init") ;
}
constructor() {
super();
console.log("Child constructor") ;
this.init();
}
}
var child = new Child() ;
console.log(child.surname);
to give the following output;
Parent constructor
Parent init
Child constructor
Child init
McClass
(which is what comparable C++ code gives)
Alas, I got this ;
Parent constructor
Child init
Child constructor
Child init
undefined
Am I doing something wrong or is this the correct intended behaviour and if so how is it justified ?
EDIT;
See MinusFour's answer below on how to achieve what I was trying to do / expecting.
As to why the observed output is the "correct" behaviour and justified ;
As Bergi pointed out (in comments) all calls to object methods in js are effectively "virtual" (the last method of that name added to the object's prototype inheritance chain being the first found and hence executed). It turns out calls are still effectively virtual in a class construction context.
C++ does not apply virtual method behaviour during construction but then again Java does and you get the same output (as above) in comparable Java code so there is a precedent for the observed behaviour.
You could do:
Parent.prototype.init.call(this);
class Parent {
init() {
console.log("Parent init");
this._surname = "McClass";
}
constructor() {
console.log("Parent constructor");
Parent.prototype.init.call(this);
}
get surname() {
return this._surname;
}
}
class Child extends Parent {
init() {
console.log("Child init");
}
constructor() {
super();
console.log("Child constructor");
this.init();
}
}
var child = new Child();
To make sure it never gets overridden, but I would suggest you just not override it in the first place.
It is expected behaviour, just because it can be seen in established ES6 class implementations that follow the specs.
this refers to current class instance, which is an instance of Child in the case when Child is instantiated - even in Parent class, because there is only one instance, and it is instanceof Child.
If Child overrides the method, it's its responsibility to provide mechanism to call it. Considering that init follows some documented convention and is the place where class initialization happens in order to make constructor leaner and more testable, it is:
class Parent {
init(){...}
constructor() {
this.init();
}
...
}
...
class Child extends Parent {
init(){
super.init();
...
}
// optional, as long as `init` contains all init logic
constructor() {
super();
}
}
Which results in a sequence:
Parent constructor
Parent init
Child init
Child constructor
If init is supposed to work totally independently in both classes, it shouldn't be overridden. Methods should be named differently, like initParent and initChild. Or any other way to avoid naming collisions can be used, e.g.:
const init = Symbol('Parent init');
class Parent {
[init](){...}
constructor() {
this[init]();
}
...
}
...
const init = Symbol('Child init');
class Child extends Parent {
[init](){...}
constructor() {
this[init](); // totally independent method
}
}

Access child constructor from base class constructor

Given the following class hierarchy: ChildClass extends ParentClass, is it possible to access the ChildClass constructor function from the ParentClass constructor? For example:
class ChildClass extends ParentClass
{
constructor()
{
super()
}
}
ChildClass.prop = 'prop'
class ParentClass
{
constructor()
{
if (this._child().prop == 'prop') // here
{
console.log("All ok");
}
}
get _child()
{
return this.constructor;
}
}
In other words, what I'm trying to do is access the child's 'static' property for verification purposes
is it possible to access the ChildClass constructor function from the ParentClass constructor?
Every Child is a Parent but not every Parent is a Child.
No. You can't. Even if possible with some dirty codes, don't do it. Rethink about your design. In the chain of Inheritance every Child should inherit the properties of Parent. Not the reverse.
Just think that there are 3 children and which children's props you get ? Bummer.
It should be this._child instead of this._child(), because child is property accessor, not a method:
class ParentClass
{
constructor()
{
if (this._child.prop == 'prop')
{
console.log("All ok");
}
}
get _child()
{
return this.constructor;
}
}
_child getter is redundant and misleading. Usually this.constructor is used directly:
class ParentClass
{
constructor()
{
if (this.constructor.prop == 'prop')
{
console.log("All ok");
}
}
}
Referring to 'child' in parent class is semantically incorrect (a parent doesn't and shouldn't 'know' about its children, and _child can be a parent itself), but referring to this is not.

Does a javascript subclass need to have the same arguments as its parent?

Here is my parent class:
class Parent {
constructor(id, label, header="") {
this.id = id;
this.label = label;
this.header = header;
}
}
You can set the header manually if you like, and it will default to "".
For some child class however I want to disallow header being set at instantiation. Will this work:
class Child extends Parent {
constructor(id, label) {
super(id, label);
this.header = "Default Header";
}
}
That is, can the constructor of a child class omit some of the arguments of its parent?
No need for that. That's how class extending works. You are passing parent attributes to child class. As for constructor it's up to you what do you want to put there.
Yes, you can do it.
You can also invoke parents constructor (with 3 arguments) in child constructor:
class Child extends Parent {
constructor(id, label) {
super(id, label, "Default header");
}
}

Categories

Resources