8

Is there any way this is possible in ES6 -- or is there a nice solution if it isn't (as seems likely):

class Parent {
    constructor() {
        console.log(this.name);
    }
}

class Child extends Parent {
     name = "Child Name";
}

const c = new Child();
// Should console.log "Child Name";

(Coming from Python, where it totally works!)

user2672537
  • 343
  • 1
  • 4
  • 11
  • 3
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Super_class_calls_with_super – Dom Apr 02 '19 at 16:28
  • I understand this example of calling a parent's method with super(). It's a question of whether the Parent constructor's reference to `this.name` points to the `name` defined in Child -- at the point that `Parent.constructor` is called. (It doesn't seem to work) – user2672537 Apr 02 '19 at 16:40

8 Answers8

7

A parent's constructor is called before the child can initialize their instance data so you can't refer to a child's instance in the parent's constructor. That's just wrong OOP design (at least in Javascript). If the parent wants access to a property in the constructor, then define and initialize the property in the constructor of the parent (the child can still use it).

Parents should not depend upon children - children depend upon the parent.

So, this is flawed OOP design. You don't show the actual problem you're trying to solve so we can't really suggest what the correct design would be for the actual problem.


To review, the order of things is:

  1. Child constructor is called
  2. Child constructor calls super(...) to execute parent constructor
  3. Parent constructor initializes its instance data
  4. After parent constructor returns, Child constructor has an opportunity to initialize its instance data

If using ES6 class definitions, you don't get to change this sequencing to something different.


Based on your latest comment to this answer, I don't think there's really a better way, than just adding the extra lines of code to each child:

class Parent {
    constructor(name) {
        if (!name) {
            // could also throw an exception here if not
            // providing a name is a programming error and is not allowed
            this.name = "Default Name";
        } else {
            this.name = name;
        }
        console.log(this.name);
    }
}

class ChildA extends Parent {
     constructor() {
         super("ChildA Name");
     }
}

class ChildB extends Parent {
     constructor() {
         super("ChildB Name");
     }
}

const c = new ChildA();
// Should console.log "ChildA Name";
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • A few misconceptions here. 1. *"First, `name = "Child Name";` does not refer to a property of the object."* OP is using class field syntax, currently a [stage 2 proposal](http://2ality.com/2017/07/class-fields.html). 2. *"Second, your child needs to call the parent's constructor with `super()`. Third, your child definition isn't correct ES6 syntax. You would need to define a child constructor."* A derived class is not required to define a `constructor`. If it's not defined, the superclass' `constructor` is called implicitly. – Jordan Running Apr 02 '19 at 17:08
  • *"Fourth, a parent's constructor is called before the child can initialize their instance data so you can't refer to a child's instance in the parent's constructor."* This part is correct, at least. – Jordan Running Apr 02 '19 at 17:09
  • This seems to be the answer I was after. In short, no, not possible. I was trying to create a data object in Parent that (among other things) contains the `name` field of the child that insantiated it. I was wondering if there was a way to do this without having to define a constructor on each child class (there are a lot) and just grab the class property from the child class. (So that the bit that changes for each child class is just one line, not four lines of constructor that I don't need except to define a single variable.) But yes, impossible as the Parent is initialised first. – user2672537 Apr 03 '19 at 09:39
  • @user2672537 - I added a code example. IMO, the cleanest way to implement this using ES6 classes involves just added the extra lines of code to define the child constructor. – jfriend00 Apr 03 '19 at 22:27
  • @JordanRunning - FYI, I cleaned up my answer per your feedback and added some additional info based on the OP's comments. – jfriend00 Apr 03 '19 at 22:27
3

To retain the value of the name property of the parent, you can simply leave the constructor out of your child class. Alternatively, if you want to update a specific property (name in this case) then you need to set this.name in the constructor AFTER a call to super() - an explicit call to super() is required before you can set this properties, otherwise JavaScript will throw an error.

Here is an example of what I try to explain above:

class Parent {
  constructor() {
    this.name = "Parent Name";
  }
}

class ChildWithConstructor extends Parent {
  constructor() {
    super();
    this.name = "Child Name";
  }

  sayName() {
    console.log(`The constructor call updated the property value set in the parent contructor: ${this.name}`)
  }
}

class ChildWithoutConstructor extends Parent {
  sayName() {
    console.log(`No constructor call retains the property values set in the parent contructor: ${this.name}`)
  }
}

const c = new ChildWithConstructor();
const nc = new ChildWithoutConstructor();
c.sayName();
nc.sayName();
Tom O.
  • 5,730
  • 2
  • 21
  • 35
  • It seems what I want to do is actually impossible. For more context, the Parent constructor was going to start constructing some kind of data object, one of the values of which was the `Child.name` variable (so that the data object keeps track of which of the many child classes was actually instantiated). But, as jfriend00's answer says, "A parent's constructor is called before the child can initialize their instance data" (which would suggest what I want to do is impossible). – user2672537 Apr 03 '19 at 09:34
2

There is a nice way
notice that this.constructor is the class regardless of where it is executing.
so we can use it to hang data.

class Parent{
  static prop = "default property"
  constructor(){
    console.log(this.constructor.name, this.constructor.prop)
  }
}

class Child extends Parent{
  static prop = "new property"
}

new Parent()
new Child()
FLAW
  • 307
  • 2
  • 12
1

the only way is to call a method named init on the child constructor and define on the parent class and use init method as your parent constructor.

in this way init will be called when child properties are ready

class Parent {
    constructor() {
      
    }
    
    init() {
      console.log(this.name);
    }
}

class Child extends Parent {
     name = "Child Name";
     
     constructor() {
      super();

      // call parent init
      this.init();
    }
}

const c = new Child();
0

From the parent class, access to the fields of the child class cannot be obtained. From child to parent - you can use super.

    class Parent {    
        constructor() {
          this.name="Parent Name";      
        };
    }

    class Child extends Parent {
      constructor(){
        super(name);
        console.log(this.name);
        this.name = "Child Name";
        console.log(this.name);    
      }
    }

    const c = new Child();
Nikita Kalitin
  • 163
  • 1
  • 1
  • 10
0

as I know, function should be opposite of attributes.

Attribute - first parent (at least this example)


Functions - first look yourself, if you don't have, look up

class Parent {
  constructor() {
    console.log(this.name());
  }
}

class Child extends Parent {
  name() {
    return "Child Name";
  }
}

const c = new Child();
// Should console.log "Child Name";

if you want to change, instead of doing this.name = 'something',
you can make a function

setName(newName){
  this.name = function(){
    return newName
  }
}

yes it is ugly to set a new value and I'm not even sure if if works.

FLAW
  • 307
  • 2
  • 12
0

for constructor only

If that props using in constructor only, pass as args.

class Parent {
    constructor(name="Parent Name") { console.log(name); }
}

class Child extends Parent {
    constructor() { super("Child Name"); }
}

for constructor and other methods

If you want, use getter or static. See @FLAW's answers.
But why don't you move the constructor logic to an other method. It's more simple.

class Parent {
    name = "Parent Name";
    hello() { console.log(this.name); }
}

class Child extends Parent {
    name = "Child Name";
}
class Parent {
    get name() { return "Parent Name"; }
    constructor() { console.log(this.name); }
}

class Child extends Parent {
    get name() { return "Child Name"; }
}

// getName() works, too.
class Parent {
    static name = "Parent Name";
    constructor() { console.log(this.constructor.name); }
}

class Child extends Parent {
    static name = "Child Name"
}
Jehong Ahn
  • 1,872
  • 1
  • 19
  • 25
  • 1
    >"But why don't you move the constructor logic to an other method. It's more simple." a very nice way for building library or framework. you just put some attributes of a class, the framework's class that you are extending will use them in its constructor. we can workaround it like `new myCLass().init()` and put construction process in parent init. so after properties are given from child class will be used in parent init. but it is ugly – FLAW Feb 04 '22 at 01:47
0

Another thing of great importance is the load order if you keep the classes in seperate files.

I had the following JS files:

  • camera.js
  • map.js

map.js

 class Map {
        constructor() {
            this.tileWidth = 32;
            this.tileHeight = 32;
        }
    }

camera.js

class Camera extends Map
{
    constructor() {
        super();
        
        this.viewPortXTiles = 20;
    }
    
    viewport()
    {
        var viewPortX = this.tileWidth * this.viewPortXTiles;
        
        console.log(this.tileWidth);
    }
}

This will return undefined if the load order loads camera.js first. So either put them in the same file or call the camera.js file: map_camera.js