Javascript inheritance: call super-constructor or use prototype chain?

87,657

Solution 1

The answer to the real question is that you need to do both:

  • Setting the prototype to an instance of the parent initializes the prototype chain (inheritance), this is done only once (since the prototype object is shared).
  • Calling the parent's constructor initializes the object itself, this is done with every instantiation (you can pass different parameters each time you construct it).

Therefore, you should not call the parent's constructor when setting up inheritance. Only when instantiating an object that inherits from another.

Chris Morgan's answer is almost complete, missing a small detail (constructor property). Let me suggest a method to setup inheritance.

function extend(base, sub) {
  // Avoid instantiating the base class just to setup inheritance
  // Also, do a recursive merge of two prototypes, so we don't overwrite 
  // the existing prototype, but still maintain the inheritance chain
  // Thanks to @ccnokes
  var origProto = sub.prototype;
  sub.prototype = Object.create(base.prototype);
  for (var key in origProto)  {
     sub.prototype[key] = origProto[key];
  }
  // The constructor property was set wrong, let's fix it
  Object.defineProperty(sub.prototype, 'constructor', { 
    enumerable: false, 
    value: sub 
  });
}

// Let's try this
function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  sayMyName: function() {
    console.log(this.getWordsToSay() + " " + this.name);
  },
  getWordsToSay: function() {
    // Abstract
  }
}

function Dog(name) {
  // Call the parent's constructor
  Animal.call(this, name);
}

Dog.prototype = {
    getWordsToSay: function(){
      return "Ruff Ruff";
    }
}    

// Setup the prototype chain the right way
extend(Animal, Dog);

// Here is where the Dog (and Animal) constructors are called
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog

See my blog post for even further syntactic sugar when creating classes. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

Technique copied from Ext-JS and http://www.uselesspickles.com/class_library/ and a comment from https://stackoverflow.com/users/1397311/ccnokes

Solution 2

The ideal way to do it is to not do Prod_dept.prototype = new Product();, because this calls the Product constructor. So the ideal way is to clone it except for the constructor, something like this:

function Product(...) {
    ...
}
var tmp = function(){};
tmp.prototype = Product.prototype;

function Prod_dept(...) {
    Product.call(this, ...);
}
Prod_dept.prototype = new tmp();
Prod_dept.prototype.constructor = Prod_dept;

Then the super constructor is called at construction time, which is what you want, because then you can pass the parameters, too.

If you look at things like the Google Closure Library you'll see that's how they do it.

Solution 3

If you have done Object Oriented Programming in JavaScript, you will know that you can create a class as follows:

Person = function(id, name, age){
    this.id = id;
    this.name = name;
    this.age = age;
    alert('A new person has been accepted');
}

So far our class person only has two properties and we are going to give it some methods. A clean way of doing this is to use its 'prototype' object. Starting from JavaScript 1.1, the prototype object was introduced in JavaScript. This is a built in object that simplifies the process of adding custom properties and methods to all instances of an object. Let's add 2 methods to our class using its 'prototype' object as follows:

Person.prototype = {
    /** wake person up */
    wake_up: function() {
        alert('I am awake');
    },

    /** retrieve person's age */
    get_age: function() {
        return this.age;
    }
}

Now we have defined our class Person. What if we wanted to define another class called Manager which inherits some properties from Person. There is no point redefining all this properties again when we define our Manager class, we can just set it to inherit from the class Person. JavaScript doesn't have built in inheritance but we can use a technique to implement inheritance as follows:

Inheritance_Manager = {};//We create an inheritance manager class (the name is arbitrary)

Now let's give our inheritance class a method called extend which takes the baseClass and subClassas arguments. Within the extend method, we will create an inner class called inheritance function inheritance() { }. The reason why we are using this inner class is to avoid confusion between the baseClass and subClass prototypes. Next we make the prototype of our inheritance class point to the baseClass prototype as with the following code: inheritance.prototype = baseClass. prototype; Then we copy the inheritance prototype into the subClass prototype as follows: subClass.prototype = new inheritance(); The next thing is to specify the constructor for our subClass as follows: subClass.prototype.constructor = subClass; Once finished with our subClass prototyping, we can specify the next two lines of code to set some base class pointers.

subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;

Here is the full code for our extend function:

Inheritance_Manager.extend = function(subClass, baseClass) {
    function inheritance() { }
    inheritance.prototype = baseClass.prototype;
    subClass.prototype = new inheritance();
    subClass.prototype.constructor = subClass;
    subClass.baseConstructor = baseClass;
    subClass.superClass = baseClass.prototype;
}

Now that we have implemented our inheritance, we can start using it to extend our classes. In this case we are going to extend our Person class into a Manager class as follows:

We define the Manager class

Manager = function(id, name, age, salary) {
    Person.baseConstructor.call(this, id, name, age);
    this.salary = salary;
    alert('A manager has been registered.');
}

we make it inherit form Person

Inheritance_Manager.extend(Manager, Person);

If you noticed, we have just called the extend method of our Inheritance_Manager class and passed the subClass Manager in our case and then the baseClass Person. Note that the order is very important here. If you swap them, the inheritance will not work as you intended if at all. Also note that you will need to specify this inheritance before you can actually define our subClass. Now let us define our subClass:

We can add more methods as the one below. Our Manager class will always have the methods and properties defined in the Person class because it inherits from it.

Manager.prototype.lead = function(){
   alert('I am a good leader');
}

Now to test it let us create two objects, one from the class Person and one from the inherited class Manager:

var p = new Person(1, 'Joe Tester', 26);
var pm = new Manager(1, 'Joe Tester', 26, '20.000');

Feel free to get full code and more comments at: http://www.cyberminds.co.uk/blog/articles/how-to-implement-javascript-inheritance.aspx

Share:
87,657

Related videos on Youtube

Jeremy S.
Author by

Jeremy S.

My Name is Jeremy and im a 29 years old developer developer from Vienna, Austria. Most of my professional life was dedicated to stuff like Java (Hibernate, Spring) Php (CakePHP) JavaScript (jQuery, ExtJS) HTML/CSS MySQL and other IT topics.

Updated on July 05, 2022

Comments

  • Jeremy S.
    Jeremy S. almost 2 years

    Quite recently I read about JavaScript call usage in MDC

    https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call

    one linke of the example shown below, I still don't understand.

    Why are they using inheritance here like this

    Prod_dept.prototype = new Product();
    

    is this necessary? Because there is a call to the super-constructor in

    Prod_dept()
    

    anyway, like this

    Product.call
    

    is this just out of common behaviour? When is it better to use call for the super-constructor or use the prototype chain?

    function Product(name, value){
      this.name = name;
      if(value >= 1000)
        this.value = 999;
      else
        this.value = value;
    }
    
    function Prod_dept(name, value, dept){
      this.dept = dept;
      Product.call(this, name, value);
    }
    
    Prod_dept.prototype = new Product();
    
    // since 5 is less than 1000, value is set
    cheese = new Prod_dept("feta", 5, "food");
    
    // since 5000 is above 1000, value will be 999
    car = new Prod_dept("honda", 5000, "auto");
    

    Thanks for making things clearer

    • Jon
      Jon almost 11 years
      The way you used it is almost right but you might want to use Object.create() instead of instantiating the base using the new keyword (could cause issues if the base constructor needs arguments). I have details on my blog: ncombo.wordpress.com/2013/07/11/…
    • event_jr
      event_jr about 10 years
      Also note that Product() is effectively called twice.
  • Ruan Mendes
    Ruan Mendes over 13 years
    I call that constructor used for setting up inheritance the surrogate constructor. Your example still forgets to reset the constructor property after setting up inheritance so you can properly detect the constructor
  • Chris Morgan
    Chris Morgan over 13 years
    @Juan: OK, updated to add Prod_dept.prototype.constructor = Prod_dept;.
  • Adaptabi
    Adaptabi over 11 years
    In EcmaScript5+ (all modern browsers), you can make it non-enumerable if you define it like this Object.defineProperty(sub.protoype, 'constructor', { enumerable: false, value: sub }); This way you'll get exactly the same "behavior" as when javascript creates a new instance of a function (the constructor is set as enumerable=false automatically)
  • Appetere
    Appetere over 10 years
    Couldn't you just simplify the extend method to two lines? Namely: sub.prototype = Object.create(base.prototype); sub.prototype.constructor = sub;
  • Ruan Mendes
    Ruan Mendes over 10 years
    @Steve Yes you can, when I first wrote this, Object.create wasn't as well supported... updating it. Note that most Object.create polyfills are implemented using the technique I showed originally.
  • Nijikokun
    Nijikokun over 10 years
    I created an easy way of utilizing this method and Object.create as seen here: gist.github.com/Nijikokun/7396092
  • Ruan Mendes
    Ruan Mendes over 10 years
    @Nijikokun Looks nice, the only downside is that you can't create a constructor just by simply using function MyClass() {}. With the approach I suggested, all you have to do is call extend to setup the prototype chain and you can keep using regular function constructors. Can you elaborate why you think calling Class(constructor) is easier?
  • Nijikokun
    Nijikokun over 10 years
    Syntactic Sugar mostly, I sacrifice a little bit for a bunch of benefits, you can still do Class(function Name () {}) if you want the naming to exist. It really wasn't about ease, it was more code readability.
  • powerc9000
    powerc9000 over 10 years
    There is a typo in Object.defineProperty you spell sub.prototype incorrectly
  • ccnokes
    ccnokes about 10 years
    So if I wanted to add methods to only the child object and its instances, the "Dog" object in this case, would you just merge the two prototypes within your extend function like this: jsfiddle.net/ccnokes/75f9P?
  • Ruan Mendes
    Ruan Mendes about 10 years
    @ccnokes It took me a while to understand what you did, but it's really clever. I'll add it to the answer
  • Lasse Christiansen
    Lasse Christiansen almost 10 years
    @ChrisMorgan I'm having trouble understanding the last line in your sample: Prod_dept.prototype.constructor = Prod_dept;. First of all, why is it needed and why does it point to Prod_dept instead of Product?
  • Chris Morgan
    Chris Morgan almost 10 years
    @LasseChristiansen-sw_lasse: Prod_dept.prototype is what will be used as the prototype of the output of new Prod_dept(). (Typically that prototype is available as instance.__proto__, though this is an implementation detail.) As for why constructor—it’s a standard part of the language and so should be provided for consistency; it’s right by default, but because we’re replacing the prototype completely, we must assign the right value again, or some things will not be sane (in this case, it would mean that a Prod_dept instance would have this.constructor == Product, which is bad).
  • Ruan Mendes
    Ruan Mendes over 9 years
    @elad.chen The approach I have described in the answer chains the prototype, mixins usually copy all the properties to an instance, not to the prototype. See stackoverflow.com/questions/7506210/…
  • elad.chen
    elad.chen over 9 years
    @JuanMendes Thanks, that helps.
  • Jacques
    Jacques over 8 years
    @JuanMendes I used the 'new' approach, but my constructors were called twice so I switched to your approach, sorted. But, I have four levels of inheritance and I've lost access mybase class methods. My 'base class' has method toggleWizard and my inheritance is: saveForGoalClass extends saveForBase extends viewModelBase extends base. Before I could call toggleWizard from saveForGoalClass, but after I implemented this method I get an exception about the function being undefined. Ideas? How does passing 'this' in the 'superConstructor.call' method effect my scenario up the inheritance chain?
  • Jacques
    Jacques over 8 years
    @JuanMendes I've added another question with a jsFiddle example that I hope will help illustrate the problem I'm seeing. stackoverflow.com/q/35696180/392591
  • Ruan Mendes
    Ruan Mendes over 8 years
    @jacques please delete the two comments above since they have been addressed