110

Is it possible to extend a class in ES6 without calling the super method to invoke the parent class?

EDIT: The question might be misleading. Is it the standard that we have to call super() or am I missing something?

For example:

class Character {
   constructor(){
      console.log('invoke character');
   }
}

class Hero extends Character{
  constructor(){
      super(); // exception thrown here when not called
      console.log('invoke hero');
  }
}

var hero = new Hero();

When I'm not calling super() on the derived class I'm getting a scope problem -> this is not defined

I'm running this with iojs --harmony in v2.3.0

xhallix
  • 2,519
  • 5
  • 31
  • 52
  • What do you mean scope problem? Are you getting an exception (and where)? – Amit Jun 26 '15 at 07:26
  • I'm getting the expection in my derived class when invoking it without calling super(). I edited my question to make it more clear:) – xhallix Jun 26 '15 at 07:27
  • What environment are you running this in? – Amit Jun 26 '15 at 07:31
  • 6
    You have no choice if you extend another class the constructor must first call super(). – Jonathan de M. Jun 26 '15 at 07:31
  • @JonathandeM. thank you, so this it is the way it is supposed to be in the future I guess then? – xhallix Jun 26 '15 at 07:33
  • At least that's the way it's implemented in babel – Jonathan de M. Jun 26 '15 at 07:36
  • This is a sign your objects should not belong the same inheritance hierarchy: constructor must be invoked to properly initialize the state of the object. If you don't care of the state - you simply should not extend it. – zerkms Jun 26 '15 at 07:41
  • @JonathandeM. - You're wrong. See [this](https://babeljs.io/repl/#?experimental=false&evaluate=true&loose=false&spec=false&code=class%20Character%20%7B%0D%0A%20%20%20constructor()%7B%0D%0A%20%20%20%20%20%20console.log('invoke%20character')%3B%0D%0A%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Aclass%20Hero%20extends%20Character%7B%0D%0A%20%20constructor()%7B%0D%0A%20%20%20%20%20%20console.log('invoke%20hero')%3B%0D%0A%20%20%20%20%20%20super()%3B%20%2F%2F%20no%20exception%0D%0A%20%20%7D%0D%0A%7D%0D%0Avar%20hero%20%3D%20new%20Hero()%3B). You only have to call `super()` sometime, not necessarily first – Amit Jun 26 '15 at 07:41
  • @Amit, good to know thanks! – Jonathan de M. Jun 26 '15 at 07:46
  • @Amit even though in this case it's true, you generally don't argument with an implementation which is not 100% compilant to the specification. – zerkms Jun 26 '15 at 07:46
  • @zerkms - 1st, Jonathan specifically stated babel's implementation. 2nd, where in the spec does it say otherwise? There are legit use cases for this – Amit Jun 26 '15 at 07:49
  • @Amit my point was: when it's a standard discussed - it makes sense to refer to the specification, not to one of its incomplete implementations. – zerkms Jun 26 '15 at 07:51
  • @zerkms but where does the standard indicate this to be illegal? – Amit Jun 26 '15 at 07:52
  • @Amit it does not. Again, your "proof" was based on the implementation, not on a standard. So I mentioned that if you want to state something in a thread that discusses a standard - you should refer to a standard, not to an incomplete implementation. – zerkms Jun 26 '15 at 07:52
  • @zerkms - I think this discussion belongs in [meta], but anyway, how would you argue that differently? The standard has no reference to this (apparently because it's not a limitation), so using a sample to show it "works" is the best proof I can think of to show it's not a limitation. – Amit Jun 26 '15 at 08:01

10 Answers10

164

The rules for ES2015 (ES6) classes basically come down to:

  1. In a child class constructor, this cannot be used until super is called.
  2. ES6 class constructors MUST call super if they are subclasses, or they must explicitly return some object to take the place of the one that was not initialized.

This comes down to two important sections of the ES2015 spec.

Section 8.1.1.3.4 defines the logic to decide what this is in the function. The important part for classes is that it is possible for this be in an "uninitialized" state, and when in this state, attempting to use this will throw an exception.

Section 9.2.2, [[Construct]], which defines the behavior of functions called via new or super. When calling a base class constructor, this is initialized at step #8 of [[Construct]], but for all other cases, this is uninitialized. At the end of construction, GetThisBinding is called, so if super has not been called yet (thus initializing this), or an explicit replacement object was not returned, the final line of the constructor call will throw an exception.

loganfsmyth
  • 135,356
  • 25
  • 296
  • 231
  • 1
    Could you suggest a way to inherit from a class without calling `super()`? – Bergi Jun 27 '15 at 21:21
  • @Bergi Care to clarify? – loganfsmyth Jun 27 '15 at 21:46
  • 4
    Do you think it's possible in ES6 to inherit from a class without calling `super()` in the constructor? – Bergi Jun 27 '15 at 21:48
  • 9
    Thanks for the edit - it's right there now; you can do `return Object.create(new.target.prototype, …)` to avoid calling the super constructor. – Bergi Mar 29 '16 at 19:20
  • @Bergi, can you please clarify what's `new.target`? I've never seen such construction used before – Max Koretskyi Mar 30 '17 at 08:25
  • 2
    @Maximus See [What is “new.target”?](http://stackoverflow.com/q/32450516/1048572) – Bergi Mar 30 '17 at 12:00
  • 1
    You should add what happens if the constructor is omitted: A default constructor will be used according to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor#Default_constructors). – kleinfreund Nov 18 '18 at 14:57
  • 1
    @Bergi `Thanks for the edit - it's right there now; you can do return Object.create(new.target.prototype, …) to avoid calling the super constructor.` Can you provide an example, please? – tonix May 01 '20 at 10:51
13

There have been multiple answers and comments stating that super MUST be the first line inside constructor. That is simply wrong. @loganfsmyth answer has the required references of the requirements, but it boil down to:

Inheriting (extends) constructor must call super before using this and before returning even if this isn't used

See fragment below (works in Chrome...) to see why it might make sense to have statements (without using this) before calling super.

'use strict';
var id = 1;
function idgen() {
  return 'ID:' + id++;
}

class Base {
  constructor(id) {
    this.id = id;
  }

  toString() { return JSON.stringify(this); }
}

class Derived1 extends Base {
  constructor() {
    var anID = idgen() + ':Derived1';
    super(anID);
    this.derivedProp = this.baseProp * 2;
  }
}

alert(new Derived1());
Amit
  • 41,690
  • 8
  • 66
  • 101
  • 2
    `"See fragment below (works in Chrome...)"` open the chrome developer console and click on "Run code snippet": `Uncaught ReferenceError: this is not defined`. Sure, you can use methods in the constructor before `super()` but you cannot use methods from the class before! – marcel Jun 26 '15 at 13:35
  • that you cannot use `this` before `super()` (your code prove it) has nothing to do with the specification immediately, but with the implementation of javascript. So, **you have to call 'super' before 'this'.** – marcel Jun 26 '15 at 13:58
  • @marcel - I think we had a lot of confusion. I was only stating (all along) that it is legal to have *STATEMENTS* before using `super`, and you were stating that it is illegal to use `this` before calling `super`. We're both right, just didn't understand each other :-) (And that exception was intentional, to show what isn't legal - I even named the property 'WillFail') – Amit Jun 27 '15 at 11:15
  • I'd say the example is quick and dirty. Sorry for being blunt. It shows that `super` doesn't have to be the first statement. But why would I want that? To generate an id before calling the base constructor? Why not use it for all the classes? `derivedProp` is `null`. It at least brings up a lot of questions. And you make it sound like you have an example of practical application. But the example is still theoretical. – x-yuri Dec 18 '20 at 18:17
  • @x-yuri this is a 5 years old answer :-) However, I still think its value is in the explanation of what is valid or invalid code, rather than what use case there is for such code. Known more is always a good thing, even if you can't see a use case at any moment in time. – Amit Dec 21 '20 at 08:19
11

The new es6 class syntax is only an other notation for "old" es5 "classes" with prototypes. Therefore you cannot instantiate a specific class without setting its prototype (the base class).

Thats like putting cheese on your sandwich without making it. Also you cannot put cheese before making the sandwich, so...

...using this keyword before calling the super class with super() is not allowed, too.

// valid: Add cheese after making the sandwich
class CheeseSandwich extend Sandwich {
    constructor() {
        super();
        this.supplement = "Cheese";
    }
}

// invalid: Add cheese before making sandwich
class CheeseSandwich extend Sandwich {
    constructor() {
        this.supplement = "Cheese";
        super();
    }
}

// invalid: Add cheese without making sandwich
class CheeseSandwich extend Sandwich {
    constructor() {
        this.supplement = "Cheese";
    }
}

If you don’t specify a constructor for a base class, the following definition is used:

constructor() {}

For derived classes, the following default constructor is used:

constructor(...args) {
    super(...args);
}

EDIT: Found this on developer.mozilla.org:

When used in a constructor, the super keyword appears alone and must be used before the this keyword can be used.

Source

marcel
  • 2,614
  • 14
  • 23
  • so if I understand you correctly, I will not be able to use any method from class Character on my class Hero, when not invoking super() ? But this seems not to be fully correct, because I can call the methods from the base class. so I guess I just need super when calling the constructor – xhallix Jun 26 '15 at 08:19
  • 1. In the OP, `this` isn't used at all. 2. JS is not a sandwich, and in ES5 you could always use `this`, even before calling any other function you like (that might or might not define the supplement property) – Amit Jun 26 '15 at 08:20
  • @amit 1. And now i can't use `this` too?! 2. My JS class represents a sandwich, and in ES6 you cannot always use `this`. I am just trying to explain es6 classes (with a metaphor), and nobody needs such destructive/unnecessary comments. – marcel Jun 26 '15 at 08:45
  • @marcel My apologies for the cynicism, but: 1. was about introducing a new problem that didn't exist in the OP. 2 is to point your attention that your claim is wrong (Which is still the case) – Amit Jun 26 '15 at 08:48
  • @marcel - See my answer – Amit Jun 26 '15 at 13:27
  • "Also you cannot put cheese before making the sandwich, so..." Yet oddly we can put cheese on the sandwich without even having cheese. "class Base { constructor () { this.cheese() } } class Derived extends Base { cheese () { console.log('swiss') } } new Derived" actually prints swiss. – Samuel Danielson Sep 10 '16 at 16:53
  • I like your example, which makes sense to newcomers :) – vintproykt Oct 04 '19 at 08:17
  • I think when we are extend parent class then the parent class constructor automatically will call in their child class. – Fawwad Jan 10 '20 at 10:57
6

You can omit super() in your subclass, if you omit the constructor altogether in your subclass. A 'hidden' default constructor will be included automatically in your subclass. However, if you do include the constructor in your subclass, super() must be called in that constructor.

class A{
   constructor(){
      this.name = 'hello';   
   }
}
class B extends A{
   constructor(){
      // console.log(this.name); // ReferenceError
      super();
      console.log(this.name);
   }
}
class C extends B{}  // see? no super(). no constructor()

var x = new B; // hello
var y = new C; // hello

Read this for more information.

Chong Lip Phang
  • 6,658
  • 5
  • 49
  • 71
4

Just registered to post this solution since the answers here don't satisfy me the least since there is actually a simple way around this. Adjust your class-creation pattern to overwrite your logic in a sub-method while using only the super constructor and forward the constructors arguments to it.

As in you do not create an constructor in your subclasses per se but only reference to an method that is overridden in the respective subclass.

That means you set yourself free from the constructor functionality enforced upon you and refrain to a regular method - that can be overridden and doesn't enforce super() upon you letting yourself the choice if, where and how you want to call super (fully optional) e.g.:

super.ObjectConstructor(...)

class Observable {
  constructor() {
    return this.ObjectConstructor(arguments);
  }

  ObjectConstructor(defaultValue, options) {
    this.obj = { type: "Observable" };
    console.log("Observable ObjectConstructor called with arguments: ", arguments);
    console.log("obj is:", this.obj);
    return this.obj;
  }
}

class ArrayObservable extends Observable {
  ObjectConstructor(defaultValue, options, someMoreOptions) {
    this.obj = { type: "ArrayObservable" };
    console.log("ArrayObservable ObjectConstructor called with arguments: ", arguments);
    console.log("obj is:", this.obj);
    return this.obj;
  }
}

class DomainObservable extends ArrayObservable {
  ObjectConstructor(defaultValue, domainName, options, dependent1, dependent2) {
    this.obj = super.ObjectConstructor(defaultValue, options);
    console.log("DomainObservable ObjectConstructor called with arguments: ", arguments);
    console.log("obj is:", this.obj);
    return this.obj;
  }
}

var myBasicObservable = new Observable("Basic Value", "Basic Options");
var myArrayObservable = new ArrayObservable("Array Value", "Array Options", "Some More Array Options");
var myDomainObservable = new DomainObservable("Domain Value", "Domain Name", "Domain Options", "Dependency A", "Depenency B");

cheers!

justyourimage
  • 149
  • 1
  • 2
  • 2
    i need an "explain like i'm five" on this one.. i feel like this is a very deep answer but complicated and there fore ignored – swyx Aug 20 '17 at 18:12
  • @swyx : the magic is inside the constructor, where 'this' refers to a different type of object depending on what type of object you're creating. E.g. if you're constructing a new DomainObservable, this.ObjectConstructor refers to a different method i.e. DomainObserveable.ObjectConstructor ; while if you're constructing a new ArrayObservable, this.ObjectConstructor refers to ArrayObservable.ObjectConstructor . – cobberboy Aug 24 '17 at 07:30
  • See my answer, I posted a much simpler example – Michael Lewis Jan 24 '19 at 17:16
  • I completely agree @swyx; this answer is doing way too much... i only skimmed it and I'm already tired. I'm feeling like "explain like I'm five AND really have to pee..." – spb Aug 12 '19 at 04:51
2

The answer by justyourimage is the easiest way, but his example is a little bloated. Here's the generic version:

class Base {
    constructor(){
        return this._constructor(...arguments);
    }

    _constructor(){
        // just use this as the constructor, no super() restrictions
    }
}

class Ext extends Base {
    _constructor(){ // _constructor is automatically called, like the real constructor
        this.is = "easy"; // no need to call super();
    }
}

Don't extend the real constructor(), just use the fake _constructor() for the instantiation logic.

Note, this solution makes debugging annoying because you have to step into an extra method for every instantiation.

Michael Lewis
  • 4,092
  • 6
  • 25
  • 38
  • Yes, this is by far the easiest method and the clearest answer - the question that I ask is... "Why, God, WHY!?"... There is a very valid reason why super() isn't automatic... you may want to pass specific parameters to your base class, so you can do some processing/logic/thinking before instantiating the base class. – LFLFM Mar 08 '19 at 13:02
1

Try:

class Character {
   constructor(){
     if(Object.getPrototypeOf(this) === Character.prototype){
       console.log('invoke character');
     }
   }
}


class Hero extends Character{
  constructor(){
      super(); // throws exception when not called
      console.log('invoke hero');
  }
}
var hero = new Hero();

console.log('now let\'s invoke Character');
var char = new Character();

Demo

Jonathan de M.
  • 8,951
  • 8
  • 45
  • 70
  • in this example you also use super() and if you leave it, you have an exception thrown. So I think it is not possible to omit this super() call in this case – xhallix Jun 26 '15 at 07:47
  • I thought your goal was not to execute parent constructor, that's what this code does. You can't get rid of super when extending. – Jonathan de M. Jun 26 '15 at 07:48
  • sorry for beeing misleading :) No i just wanted to know if it is really necessary to use super() since I wonder about the syntax because in other languages we do not have to invoke the super method when calling the constructor for the derived class – xhallix Jun 26 '15 at 07:50
  • "I thought your goal was not to execute parent constructor, that's what this code does." --- it does not. It still executes the parent constructor. – zerkms Jun 26 '15 at 07:52
  • 1
    According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes, `A constructor *can* use the super keyword to call the constructor of a parent class.`, so I would say wait for ES6 release – Jonathan de M. Jun 26 '15 at 07:52
  • @zerkms I meant execute some code in the constructor. – Jonathan de M. Jun 26 '15 at 07:53
  • 3
    "so I would say wait for ES6 release" --- it already was released already, http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf – zerkms Jun 26 '15 at 07:56
1

I would recommend to use OODK-JS if you intend to develop following OOP concepts.

OODK(function($, _){

var Character  = $.class(function ($, µ, _){

   $.public(function __initialize(){
      $.log('invoke character');
   });
});

var Hero = $.extends(Character).class(function ($, µ, _){

  $.public(function __initialize(){
      $.super.__initialize();
      $.log('invoke hero');
  });
});

var hero = $.new(Hero);
});
OBDM
  • 177
  • 3
1

Simple solution: I think its clear no need for explanation.

class ParentClass() {
    constructor(skipConstructor = false) { // default value is false
        if(skipConstructor) return;
        // code here only gets executed when 'super()' is called with false
    }
}
class SubClass extends ParentClass {
    constructor() {
        super(true) // true for skipping ParentClass's constructor.
        // code
    }
}
  • I'm not sure why all the above boilerplate code, and I'm not exactly sure if there are side effects to this approach. It works for me with no issue. – MyUserInStackOverflow Mar 06 '17 at 17:44
  • 1
    One issue: if you want to extend your SubClass again, you'll have to build the skipConstructor feature into each SubClass' constructor – Michael Lewis Jan 24 '19 at 17:13
1

@Bergi mentioned new.target.prototype, but I was looking for a concrete example proving that you can access this (or better, the reference to the object the client code is creating with new, see below) without having to call super() at all.

Talk is cheap, show me the code... So here is an example:

class A { // Parent
    constructor() {
        this.a = 123;
    }

    parentMethod() {
        console.log("parentMethod()");
    }
}

class B extends A { // Child
    constructor() {
        var obj = Object.create(new.target.prototype)
        // You can interact with obj, which is effectively your `this` here, before returning
        // it to the caller.
        return obj;
    }

    childMethod(obj) {
        console.log('childMethod()');
        console.log('this === obj ?', this === obj)
        console.log('obj instanceof A ?', obj instanceof A);
        console.log('obj instanceof B ?',  obj instanceof B);
    }
}

b = new B()
b.parentMethod()
b.childMethod(b)

Which will output:

parentMethod()
childMethod()
this === obj ? true
obj instanceof A ? true
obj instanceof B ? true

So you can see that we are effectively creating an object of type B (the child class) which is also an object of type A (its parent class) and within the childMethod() of child B we have this pointing to the object obj which we created in B's constructor with Object.create(new.target.prototype).

And all this without caring about super at all.

This leverages the fact that in JS a constructor can return a completely different object when the client code constructs a new instance with new.

Hope this helps someone.

tonix
  • 5,862
  • 10
  • 61
  • 119