0

I thought I had a good understanding on how to properly extend classes in JavaScript, but when extending a subclass I run into an endless loop when I override a method, and the call the parent method from the child class. I'm either doing it wrong, or you just shouldn't subclass this way in JavaScript.

Can anybody help educate me please?

var Grand = function() {
 this.test();
};

Grand.prototype.constructor = Grand;

Grand.prototype.test = function() { 
 console.log( "Grand!")
};



var Parent = function() {
  this.supr.constructor.call( this );
};

Parent.prototype = Object.create( Grand.prototype );
Parent.prototype.constructor = Parent;
Parent.prototype.supr = Grand.prototype;

Parent.prototype.test = function() { 
 this.supr.test.call( this );
  console.log( "Parent!" );
};



var Child = function() {
  this.supr.constructor.call( this );
};

Child.prototype = Object.create( Parent.prototype );
Child.prototype.constructor = Child;
Child.prototype.supr = Parent.prototype;

Child.prototype.test = function() { 
 this.supr.test.call( this );
  console.log( "Child!" );
};



var g = new Grand(); // Outputs "Grand!"
var p = new Parent(); // Outputs "Grand!" "Parent!"
var c = new Child(); // Error: Endless Loop!

I would expect the console to log "Grand!", "Parent!", "Child!" when instantiated a new Child(), but instead I get an endless loop.

I'm coming from an ActionScript background, so creating classes in JavaScript still throws me some curve balls. Thanks for the help in advance!

SpaceCowboy2071
  • 1,125
  • 1
  • 13
  • 20
  • 1
    There is no really a concept of Class and Subclass in JavaScript (apart of sugar syntax in ES6). JavaScript is prototype base. – GibboK Aug 24 '16 at 07:40

2 Answers2

4

I recommend switching to es6. This prototyping can be a real mess and is harder to keep track of. But then if you need this in the browser, you should transpile your code to es5 with babel or such. In the Node env, it's fine without as long as you have a recent up-to-date version. Some of the latest browser supports it as well.

class Grand {
    constructor() {
        this.test()
    }

    test() {
        console.log( "Grand!")
    }
}

class Parent extends Grand {
    test() {
        super.test()
        console.log( "Parent!" )
    }
}

class Child extends Parent {
    test() {
        super.test()
        console.log( "Child!" )
    }
}

let g = new Grand(); // Outputs "Grand!"
let p = new Parent(); // Outputs "Grand!" "Parent!"
let c = new Child(); // Outputs "Grand!" "Parent! "child!"

Not only is it more readable, but it's less code and more understandable.

Pang
  • 9,564
  • 146
  • 81
  • 122
Endless
  • 34,080
  • 13
  • 108
  • 131
  • Honestly, I would rather go with ES6 rather than learning TypeScript or Dart. I'm not too familiar with Babel, is it battle tested and robust enough that I can depend on it for client projects? Thanks! – SpaceCowboy2071 Aug 24 '16 at 08:40
  • Babel is as solid as it gets. And please don't code in some other language that transform to javascript like Dart or coffescript. coffescript is the most loved-hated thing on the internet – Endless Aug 24 '16 at 09:05
  • 1
    If the code can't be executed in any browser/node environment then don't use it cuz in the end it will support whatever some made up language tried to fulfill. They also need to keep up with the newest javascript version or they will have fallen behind and died out. – Endless Aug 24 '16 at 09:13
1

The problem is with this bit of code:

var Parent = function() {
  this.supr.constructor.call( this );  
};

Consider what happens when this code executes:

var c = new Child();

Here this is the variable c, so this.supr.constructor will always be the parent's constructor as setup in these lines of code:

Child.prototype.supr = Parent.prototype;  // i.e. this.supr = Parent.prototype
Parent.prototype.constructor = Parent;  // i.e. this.supr.constructor = Parent

So, when the Child's constructor invokes this.supr.constructor.call( this ); it executes the Parent function, and the parent function again executes this.supr.constructor.call( this ); resulting the Parent function being invoked again, causing the endless loop.

A fix is to invoke base class functions as follows:

var Child = function() {
  Child.prototype.supr.constructor.call( this );
};

More details in this post

Mohammad Usman
  • 37,952
  • 20
  • 92
  • 95
Dhananjay
  • 544
  • 3
  • 7