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

JavascriptClassInheritanceEcmascript 6

Javascript Problem Overview


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

Javascript Solutions


Solution 1 - Javascript

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

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

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

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

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!

Solution 6 - Javascript

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.

Solution 7 - Javascript

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

Solution 8 - Javascript

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

Solution 9 - Javascript

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

Solution 10 - Javascript

@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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionxhallixView Question on Stackoverflow
Solution 1 - JavascriptloganfsmythView Answer on Stackoverflow
Solution 2 - JavascriptmarcelView Answer on Stackoverflow
Solution 3 - JavascriptAmitView Answer on Stackoverflow
Solution 4 - JavascriptChong Lip PhangView Answer on Stackoverflow
Solution 5 - JavascriptjustyourimageView Answer on Stackoverflow
Solution 6 - JavascriptMichael LewisView Answer on Stackoverflow
Solution 7 - JavascriptJonathan de M.View Answer on Stackoverflow
Solution 8 - JavascriptOBDMView Answer on Stackoverflow
Solution 9 - JavascriptMyUserInStackOverflowView Answer on Stackoverflow
Solution 10 - JavascripttonixView Answer on Stackoverflow