Multi-level inheritance in Javascript

Ladies and gentlemen, I present you two snippets of code and ask for your advice.

The first one is mine, the second one belongs to the coder by the nickname vipatron. This discussion started as my attempt to grasp the topic of inheritance: Use Inheritance So You Dont Repeat Yourself. How inheritance is declared?

Naturally, some new questions arose, some additional materials were read and now I’m stuck once again.

Initially I tried to create multi-level structure with three constructors descending one from another and finally one object created by the last constructor and inheriting all the properties of all the constructors and prototypes above.

Both code snippets have declaration of three classes, one object and a series of checks at the bottom to find out whether our bottom-most object has all the properties we wanted.

Code #2 has mainTask, blod and secondaryTask as own properties of the final object(tony) which is what I was intending to have, but if we add properties through prototypes (Code #1) the whole system doesn’t work as planned, so I have several questions:

  1. How to create bottom-most object (tony) and pass all the values of the constructors above (species, name, breed)? (Example #1) (if it is possible, of course)

  2. How to access all the properties that has been added through prototypes? I expected that prototypeProps will contain mainTask, blood, secondaryTask (Example #1)

  3. How it is used in real world? Perhaps it will be more instructive to see a real world example with concrete use of this technique.

  4. If we don’t use prototypes, everything works fine (Example #2), but as I understand, the use of prototypes is recommended for complex data structures, this makes program execution faster. Is there a way for mainTask, blood, secondaryTask to be displayed in prototypeProps by adding them through prototypes (Example #1)?

  5. Which of these two approaches is used in real life?

And the final question is:

Is this something that can be done at all or I’m just doing some work that has no use anywhere else?

My expected result was:

console.log(ownProps) => species: canis lupus familiaris, name: dog, breed: scottishTerrier, likes: food

console.log(prototypeProps) => mainTask: survive and reproduce, blod: warm, secondaryTask: help human

Or just another way of containing all this information within the bottom-most object (tony)

I will appreciate your thoughts and hints on this issue.

Code #1

var Creature = function (species) {
    this.species = species;
};
Creature.prototype = {
    constructor: Creature,
    mainTask: "Survive and reproduce",
};

var Mammal = function (name) {
    Creature.call(this);
    this.name = name;
};
Mammal.prototype = {
    constructor: Mammal,
    blood: "Warm"
};
Mammal.prototype = Object.create(Creature.prototype);
Mammal.prototype.constructor = Mammal;

var Dog = function (breed) {
    Mammal.call(this);
    this.breed = breed;
};
Dog.prototype = {
    constructor: Dog,
    secondaryTask: "Help human"
};

Dog.prototype = Object.create(Mammal.prototype);
Dog.prototype.constructor = Dog;
let tony = new Dog("ScottishTerrier");
tony.likes = "food";

let ownProps = [];
let prototypeProps = [];
for (let property in tony) {
  if(tony.hasOwnProperty(property)) {
    ownProps.push(property);
  } else {
    prototypeProps.push(property);
  }
}
console.log(ownProps)
console.log(prototypeProps)
console.log(tony)

Code #2

function Creature () {
    this.mainTask = "survive and reproduce";
  }
  
  function Mammal () {
    Creature.call(this);
    this.blood = "warm";
  }
  
  Mammal.prototype = Object.create(Creature.prototype); 
  Mammal.prototype.constructor = Mammal; 
  
  function Dog () {
    Mammal.call(this);
    this.secondaryTask = "help human";
  }
  Dog.prototype = Object.create(Mammal.prototype);
  Dog.prototype.constructor = Dog;
  
  let myDogRex = new Dog();
  console.log(myDogRex);
  console.log("myDogRex.mainTask: ", myDogRex.mainTask);
  console.log("myDogRex.blood: ", myDogRex.blood);
  console.log("myDogRex.secondaryTask:", myDogRex.secondaryTask);
  
  let tony = new Dog("ScottishTerrier");


let ownProps = [];
let prototypeProps = [];
for (let property in tony) {
  if(tony.hasOwnProperty(property)) {
    ownProps.push(property);
  } else {
    prototypeProps.push(property);
  }
}
console.log(ownProps)
console.log(prototypeProps)
console.log(tony)

Question 1 - you cannot pass all the values, at least not in an easy way :slight_smile: (i.e. you can pass the arguments of one function to another only if one is defined in another, they are nested), and I am not sure why you want that in this particular case. Values are supposed to be specific to instances (this is the whole point of parameterizing a function, because you know that the context will vary a lot). Inheritance is supposed to transfer over properties, not values.

Question 2 - in general when you pass inheritance across constructors(i.e. Dog.prototype = Object.create(Animal.prototype)), make sure that this is the first thing you do. Only after this you should override the constructor(returning it back to what it is supposed to be) and add unique to the specific class properties on the prototype object. In your code (Example 1 snippet) you are losing the specific class properties like secondaryTask and blood because you define them first and then override them with the Creature.prototype.

1 Like

[EDIT]: I noticed that the proof-of-concept console.log statements negleted the mainTask property. I’ve updated this post and the linked pen accordingly.

Sorry for being away from the forum for a while. It’s been one thing after another. But, since you are working to improve your knowledge of underlying programming concepts, I felt I cannot abandon you to your confusion. I’ve created a codepen in which I modified your code (link included below, so if you want to simply open the console, run the code, and follow the results alongside it, which I recommend, you can):

Side Note: I think I understand what you meant by your property names, but I wasn’t sure, so I included ‘species’ in quotes in the console output because technically, mammals are a class of animal, and there is no “Creature” in the taxonomy. But, that’s irrelevant to the coding lesson, so let’s get to it:

I included two ways to create prototype objects for a class. There are others, but as MDN says, declaring the prototype of a Constructor using the new operator is the fastest and most widely supported., so I just showed you two variations on that theme:

  1. Declare the prototype of a subclass to be an instance of the superclass using the new operator.
  2. Add the prototypal properties you want the subclass to have onto the prototype.
    1. The first way is just by serial declaration of the prototypes (what the FCC curriculum teaches).
    2. The second is to use Object.assign() to declare them all at once, and merge them into the instance of the superclass.

The code is below, but mostly, I wanted to show you the console output so you can understand that you are on the right track, you were just running into logical errors. (After the declaration of each class, I created a generic instance of that class (not a subclass) to show what properties a direct instance of that class (Creature, Mammal, Dog) has.

console.clear();
var Creature = function (species) {
    this.species = species;
};
Creature.prototype = { //the object that will be cloned when Creature is used as the operand of new
    constructor: Creature,
    mainTask: "Survive and reproduce",
};

//Proof-of-concept Code:
const genericCreature = new Creature("Jabberwocky");
console.log("our generic Creature's main task:", genericCreature.mainTask);
console.log("our generic Creature's species:", genericCreature.species);
console.log("=============================");

var Mammal = function (name) {
    this.name = name;
};
/* OLD CODE: */
// Mammal.prototype = {
//     constructor: Mammal,
//     blood: "Warm"
// };
// Mammal.prototype = Object.create(Creature.prototype);
/* NEW CODE: */
Mammal.prototype = new Creature("mammal");
Mammal.prototype.blood = "Warm";
/* end new code */
Mammal.prototype.constructor = Mammal;

//Proof-of-concept Code:
const genericMammal = new Mammal("Elephant");
console.log("our generic Mammal's main task:", genericMammal.mainTask);
console.log("our generic Mammal's 'species':", genericMammal.species); //note the capitalization in the output
console.log("our generic Mammal's blood:", genericMammal.blood);
console.log("our generic Mammal's name:", genericMammal.name);
console.log("=============================");


var Dog = function (breed) {
    this.breed = breed;
};
/* OLD CODE: */
// Dog.prototype = {
//     constructor: Dog,
//     secondaryTask: "Help human"
// };
/* NEW CODE: Equivalent to new code for Mammal.prototype definition above, but perhaps more succinct, and occupying one statement, thus potentially less error-prone? (Really, just a matter of style) */
Dog.prototype = Object.assign( //assigns all the "own" properties of the arguments 2+ to argument 1.
  new Mammal("dog"), //let's start with a copy of Mammal.prototype on which we execute the function Mammal ONCE, with the argument *name* set to 'dog'
  {
    constructor: Dog,
    secondaryTask: "Help human"
  } // then let's declare an object literal of all the key-value pairs we want all Dogs to have in common, and assign those properties to the instance of Mammal we declared as the first argument.
);
/* end new code */

//Proof-of-concept Code:
const genericDog = new Dog("Pitbull");
console.log("our generic Dog's main task:", genericDog.mainTask);
console.log("our generic Dog's 'species':", genericDog.species); //note the capitalization in the output
console.log("our generic Dog's blood:", genericDog.blood);
console.log("our generic Dog's name:", genericDog.name);
console.log("our generic Dog's secondary task:", genericDog.secondaryTask);
console.log("our generic Dog's breed:", genericDog.breed);
console.log("=============================");

let tony = new Dog("ScottishTerrier");
tony.likes = "food";

let ownProps = [];
let prototypeProps = [];
for (let property in tony) {
  if(tony.hasOwnProperty(property)) {
    ownProps.push(property);
  } else {
    prototypeProps.push(property);
  }
}
console.log("tony's own props:", ownProps)
console.log("tony's inherited props:", prototypeProps)
console.log("tony", tony)

Hi! Sorry, it took me so long to answer your post, it was extremely helpful, but the more I dive into this topic, the deeper it seems. Right now, I’m taking a couple of courses specifically on JS objects and inheritance to find my way around all the intricate details and quirks of JavaScript when it comes to objects. Thank you for your time and examples you’ve provided for me, they’re now in my library, waiting for me to get back to them as soon as I’ve cleared up all the necessary basics of JS objects.