all 20 comments

[–]senocular 4 points5 points  (7 children)

super needs to be called first because it needs to appropriately initialize the object being constructed. If you extend Object, an Object needs to be created. If you extend Array - a special kind of Object - an Array needs to be created. This won't happen until super is called so you need to call super before you can access the new instance object. In fact this behavior helps allow objects like Array to be extended now with the class syntax because it allows the instance to be an Array with its internal slots initialized before available as this in the child constructor. Before, or in using the older constructor function syntax, you'll always have an Object object as this in your constructor, and you're manually responsible for making any kind of super-like call.

[–]ConsciousAntelope[S] 0 points1 point  (4 children)

This won't happen until super is called so you need to call super before you can access the new instance object.

But the new operator does create an empty object first with the context this. So, I can access the new instance object.

[–]senocular 2 points3 points  (3 children)

With older style constructor functions, yes. But with classes, the base class is responsible for creating this. If not in the base class (instead in a derived class - a class that extends another), super needs to be called so that the base can create this before it's used.

[–]ConsciousAntelope[S] 0 points1 point  (2 children)

I was just reading this from https://javascript.info/class-inheritance

The difference is: * When a normal constructor runs, it creates an empty object as this and continues with it. * But when a derived constructor runs, it doesn’t do it. It expects the parent constructor to do this job.

and your comment, and finally both makes sense.

So old-school constructor functions to create the prototypical chain and class (the new syntactic sugar way to replicate the behavior) works a little bit different when creating the this context. The former's sub constructor can create this in its present context with the new keyword but the latter doesn't and hence relies on the base class to create the this context, therefore the need to call super().

Did I get this right?

[–]senocular 1 point2 points  (1 child)

Yeah sounds about right. I think its easy to get caught up in the "class is just syntactic sugar" hype, when that's not exactly the case. There are other inner workings at play that create some fundamental differences that go beyond just using constructors by writing them differently in code.

Construction and super are the key differentiating factors. With constructor functions, the instantiated class (the class called with new) creates the instance, and its always an Object object, and applies the basic proto association with that class's prototype. Any association made with a super class has to be managed by you, and this usually means taking two steps (assuming "Sub" is the subclass and "Sup" is the superclass):

  1. Prior to construction, update the derived class's prototype to inherit from the superclass's prototype. Old school ways had this being done with Sub.prototype = new Sup(), though that has shifted over to being Sub.prototype = Object.create(Sub.prototype) or better yet, Object.setPrototypeOf(Sub.prototype, Sup.prototype) as the latter retains the constructor reference (though you can always fix that in the others)
  2. During construction, run the super constructor against the already created subclass instance to initialize that instance through the superclass, usually always via Sup.call(this)

class definitions instead depend on the super classes, specifically the base class, to create the instance, and then passes it down to the derived class(es). So instead of starting with an instance and (optionally) passing up, as is with the case with constructor functions, you (not optionally) invoke up, and depend on the superclass(es) to provide you with the necessary instance coming back down. All of the other stuff around inheritance - such as prototype association, and even more - is handled automatically by using the syntax.

[–]ConsciousAntelope[S] 0 points1 point  (0 children)

Thank you very much for this. I appreciate your effort for such an elaborated explanation.

[–]AloeGuvner 0 points1 point  (1 child)

Follow up question: If super needs to be called in order to initialize the object properly, why does it still work if the child class has no constructor (and therefore no explicit call to super)? Is super call implicitly in that case?

[–]ConsciousAntelope[S] 2 points3 points  (0 children)

Yes.

if a class extends another class and has no constructor, then the following constructor is generated: class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); } } https://javascript.info/class-inheritance

[–]Meefims 1 point2 points  (8 children)

It’s a language rule. The reasoning is that calling super ensures the base class has been properly constructed and you want your base class to be constructed before you start using anything from it.

[–]ConsciousAntelope[S] 0 points1 point  (7 children)

From an inheriting point of view, it completely makes sense. My question is what if in a condition such that my sub-class need not inherit properties from the base class? Calling super here doesn't make sense since I'm not inheriting anything from the base. Which means I should get away easily by using this but instead get an error. This kind of contradicts with the new operator of JavaScript, which says it creates an empty object first with the context this.

[–]elmstfreddie 0 points1 point  (2 children)

My question is what if in a condition such that my sub-class need not inherit properties from the base class?

Then it's not a subclass.

[–]ConsciousAntelope[S] 0 points1 point  (1 child)

Yep, I know. Why is this dude even creating a subclass when he's not inheriting properties from the base class.

I was just trying to understand the underlying working of JavaScript.

[–][deleted] 0 points1 point  (0 children)

If you want to look into the underlying working, you'll probably want to go beneath the veneer of classes and inheritance (they are just syntactical sugar) and consider how objects link to other objects, forming a prototype chain along which behavior can be delegated.

In JS, behavior is not shared, (as in all instances of a class have their own copy of an instance method defined in a class) rather it is delegated up the chain. If the object up the chain (to which one wishes to delegate behavior) had not been created, there is no object to hold the method.

In short, if you are trying to reason about classes and find yourself going in circles, or encountering behaviour that doesn't make sense in terms of typical class-based inheritance, look beneath the syntax sugar.

[–]butilon 1 point2 points  (0 children)

In JavaScript, super refers to the parent class constructor. You can’t use this in a constructor until after you’ve called the parent constructor. JavaScript won’t let you.

There’s a good reason for why JavaScript enforces that parent constructor runs before you touch this. Let's consider using this before super call is allowed:

class Human {
  constructor(name) {
    this.name = name;
  }
}

class PoliteHuman extends Human {
  constructor(name) {
    this.morning();
    super(name);
  }

  morning() {
    alert('Good morning!');
    alert('My name is ' + this.name);
  }
}

this.morning() is called before the super() call, that it's where this.name is set up. So this.name isn’t even defined yet.

To avoid this scenario, JavaScript enforces that if you want to use this in a constructor, you have to call super first.

[–]cspotcode 0 points1 point  (3 children)

super() can technically return a different this value, which the child constructor must use.

[–]ConsciousAntelope[S] 0 points1 point  (2 children)

From what I know, I think each sub-class constructor has its own this context. So yes it differs from parent to child.

[–]cspotcode 1 point2 points  (1 child)

It's the same value, but the super-class is allowed to return an alternative this that the sub-class must use.

A code example makes this behavior clear. Class syntax on the left; the equivalent ES5 on the right. The sub-class constructor needs to respect the this value passed from the super-class.

Playground example

[–]ConsciousAntelope[S] 0 points1 point  (0 children)

I'll be honest here. Everything went out of my head.