Search code examples
javascriptinheritanceconstructorprototype

How to inherit right from parent prototype JS


The question - how to get the right methods inheriting from parent (Animal) to child (Rabbit) prototypes that will does not override the parent prototype props after creating an object, based on child prototype. I mean, the creating object (rabbit) must gets prototype method (.prototype.walk) from Rabbit prototype, not from parent (Animal).

I know that we can make easy solution and move the method this.walk behind the constructor Animal and create prototype method Animal.prototype.walk... But I wonder if we can do this without pushing this.walk out of constructor brackets? Is that some JS trik?

I'll be grateful for the any answer, thanks!

function Animal(name) {
  this.name = name;

  this.walk = function() {
    console.log( "walk " + this.name );
  };

}

// Animal.prototype.walk = function() {   //// we can not use this trik and 
//    console.log( "walk " + this.name ); //// delete this.walk function in 
//}                                       //// constructor


function Rabbit(name) {
  Animal.apply(this, arguments);
}

Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.constructor = Rabbit;

Rabbit.prototype.walk = function() {
  console.log( "jump " + this.name );
};

var rabbit = new Rabbit("Rabbit");
rabbit.walk(); // for now we get "walk Rabbit". But need - "jump Rabbit"

Solution

  • TL;DR: You are mixing two different ways of defining methods in JavaScript. You should pick one way of doing it and stick with it.


    With Animal you are defining the walk method within the body of the constructor. This will create a new function each time the constructor is called. It's easy to read, but will waste memory as every instance will have its own identical walk method.

    With Rabbit you are taking advantage of the Rabbit's prototype, so that each instance instead shares a single walk function. You can think of prototypes in JS as backup objects. JS first looks for a property on the instance itself. If not found, it will work it's way up the "prototype chain" until it finds the property or runs out of prototypes.

    For example, with your code, if I called rabbit.toString(), I would end up traveling up the chain something like this:

    rabbit.toString           --> undefined
    Rabbit.prototype.toString --> undefined
    Animal.prototype.toString --> undefined
    Object.prototype.toString --> ƒ toString() { [native code] }
    

    However, when you call rabbit.walk() it never makes it even to the first prototype, because Animal.apply(this, arguments) added a walk function directly onto the rabbit instance.

    The way to fix this is to define your two walk methods either both on a prototype (preferable) or both in the constructor body.


    Alternatively, you could take advantage of ES6 syntax, which will put your walk method on the prototypes in the background, but looks a lot cleaner than doing it manually:

    class Animal {
      constructor (name) {
        this.name = name
      }
    
      walk () {
        console.log(`walk ${this.name}`)
      }
    }
    
    class Rabbit extends Animal {
      walk () {
        console.log(`jump ${this.name}`)
      }
    }
    
    const rabbit = new Rabbit('Rabbit')
    rabbit.walk()  // "jump Rabbit"