How to extend a class without having to use super in ES6?

120,525

Solution 1

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.

Solution 2

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

Solution 3

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());

Solution 4

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.

Solution 5

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.

Share:
120,525
xhallix
Author by

xhallix

Updated on May 02, 2020

Comments

  • xhallix
    xhallix about 4 years

    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
    xhallix about 9 years
    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
  • Jonathan de M.
    Jonathan de M. about 9 years
    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.
  • xhallix
    xhallix about 9 years
    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
  • zerkms
    zerkms about 9 years
    "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.
  • Jonathan de M.
    Jonathan de M. about 9 years
    According to developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…, 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.
    Jonathan de M. about 9 years
    @zerkms I meant execute some code in the constructor.
  • zerkms
    zerkms about 9 years
    "so I would say wait for ES6 release" --- it already was released already, ecma-international.org/publications/files/ECMA-ST/Ecma-262.p‌​df
  • xhallix
    xhallix about 9 years
    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
  • Amit
    Amit about 9 years
    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)
  • marcel
    marcel about 9 years
    @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.
  • Amit
    Amit about 9 years
    @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
    Amit about 9 years
    @marcel - See my answer
  • marcel
    marcel about 9 years
    "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
    marcel about 9 years
    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'.
  • Amit
    Amit almost 9 years
    @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')
  • Bergi
    Bergi almost 9 years
    Could you suggest a way to inherit from a class without calling super()?
  • loganfsmyth
    loganfsmyth almost 9 years
    @Bergi Care to clarify?
  • Bergi
    Bergi almost 9 years
    Do you think it's possible in ES6 to inherit from a class without calling super() in the constructor?
  • Bergi
    Bergi about 8 years
    Thanks for the edit - it's right there now; you can do return Object.create(new.target.prototype, …) to avoid calling the super constructor.
  • Abhishek Avadhoot
    Abhishek Avadhoot almost 8 years
    "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.
  • MyUserInStackOverflow
    MyUserInStackOverflow over 7 years
    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.
  • Max Koretskyi
    Max Koretskyi about 7 years
    @Bergi, can you please clarify what's new.target? I've never seen such construction used before
  • Bergi
    Bergi about 7 years
  • swyx
    swyx almost 7 years
    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
  • cobberboy
    cobberboy almost 7 years
    @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 .
  • kleinfreund
    kleinfreund over 5 years
    You should add what happens if the constructor is omitted: A default constructor will be used according to MDN.
  • Michael Lewis
    Michael Lewis over 5 years
    One issue: if you want to extend your SubClass again, you'll have to build the skipConstructor feature into each SubClass' constructor
  • Michael Lewis
    Michael Lewis over 5 years
    See my answer, I posted a much simpler example
  • LFLFM
    LFLFM over 5 years
    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.
  • spb
    spb almost 5 years
    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..."
  • vintprox
    vintprox over 4 years
    I like your example, which makes sense to newcomers :)
  • Fawwad
    Fawwad over 4 years
    I think when we are extend parent class then the parent class constructor automatically will call in their child class.
  • tonix
    tonix about 4 years
    @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?
  • x-yuri
    x-yuri over 3 years
    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.
  • Amit
    Amit over 3 years
    @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.