Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ES6 calling super() doesn't properly initialize parent class

I have the following code structure, I try to initialize parent class by calling super(), but when i call this._init() it calls the one of the child. any help how can I fix this?

class Parent {
    constructor() {
    console.log('P constructor()');
    this._init();
  }

  _init() {
    console.log('P _init()');
    this.parentProp = 'parent';
  }
}

class Child extends Parent {
    constructor() {
    console.log('C constructor');
    super();
    this._init();
  }

  _init() {
      console.log('C _init()');
    this.childProp = 'child';
  }

  test() {
    console.log(this.childProp + ' ' + this.parentProp);
  }
}

let child = new Child();
child.test();

Here's the output of the above code:

C constructor()
P constructor()
C _init()
C _init()
child undefined
like image 446
Amr M. AbdulRahman Avatar asked Oct 28 '25 09:10

Amr M. AbdulRahman


2 Answers

Child#_init is getting called because that's what the _init property on the object refers to when this._init() (in Parent) is called. What happens (leaving out some details) is:

  1. new creates a new object whose [[Prototype]] is Child.prototype. Child.prototype's [[Prototype]] is Parent.prototype.
  2. new calls Child.
  3. Child calls Parent.
  4. this._init() looks up the _init property on the object. Since the object doesn't have its own _init property, the JavaScript engine looks to its [[Prototype]]. Child.prototype does have an _init property, so the engine uses that one.

As for solving it: JavaScript classes have only one constructor, so there's no real purpose to having a separate _init function.1 That's what constructors are for. Despite their name, they don't construct objects, they initialize them. So just put _init's code in the constructor itself:

class Parent {
  constructor() {
    console.log('P constructor');
    this.parentProp = 'parent';
  }
}

class Child extends Parent {
  constructor() {
    console.log('C constructor');
    super();
    this.childProp = 'child';
  }

  test() {
    console.log(this.childProp + ' ' + this.parentProp);
  }
}

let child = new Child();
child.test();

Alternately, just remove the this._init() call from Child entirely and have Child#_init call super._init(). I know you've said in a comment you think that's poor practice (it isn't, it's standard practice), but if you want to break out _init to separate functions, that's what you do. But doing so violates the principal, well-established cross-language, that calling overrideable methods from a constructor (Parent calling this._init()) is a Bad Idea™. :-)

If you absolutely insist on separating the code out into a function, and don't want to use super._init() in Child#_init, it'll need to be separate from the class:

let Parent = (function() {
  class Parent {
    constructor() {
      console.log('P constructor');
      initParent.call(this);
    }
  }

  function initParent() {
    console.log("initParent");
    this.parentProp = 'parent';
  }

  return Parent;
})();

let Child = (function() {
  class Child extends Parent {
    constructor() {
      console.log('C constructor');
      super();
      initChild.call(this);
    }

    test() {
      console.log(this.childProp + ' ' + this.parentProp);
    }
  }

  function initChild() {
    console.log("initChild");
    this.childProp = 'child';
  }

  return Child;

})();

let child = new Child();
child.test();

1 I could see using a method in a language with overloaded constructors — although even there I advocate having them call each other rather than a utility method — but not in JavaScript.

like image 52
T.J. Crowder Avatar answered Oct 31 '25 00:10

T.J. Crowder


The best solution is not to use an init method at all. A constructor should not call overwritable methods:

class Parent {
  constructor() {
    console.log('P constructor()');
    this.parentProp = 'parent';
  }
}

class Child extends Parent {
  constructor() {
    console.log('C constructor');
    super();
    this.childProp = 'child';
  }
  test() {
    console.log(this.childProp + ' ' + this.parentProp);
  }
}

let child = new Child();
child.test();

Alternatively, in your case it would have worked to call the super method in _init and not to call _init from the Child constructor:

class Child extends Parent {
  constructor() {
    console.log('C constructor');
    super();
  }
  _init() {
    super._init();
    console.log('C _init()');
    this.childProp = 'child';
  }
  …
like image 35
Bergi Avatar answered Oct 31 '25 02:10

Bergi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!