Prototype and Inheritance in JS (non-class)

https://www.digitalocean.com/community/tutorials/understanding-prototypes-and-inheritance-in-javascript

note:

It is important to note that x.__proto__ is a legacy feature and should not be used in production code, and it is not present in every modern browser. However, we can use it throughout this article for demonstrative purposes.

Use Object.getPrototypeOf(x) instead.

Creating objects through Constructor Functions

Because each character will share many characteristics, such as having a name, and a level, we use a constructor functions to create templates for those common traits of each hero.

Every hero has a name, and a certain level of experience

We have created a constructor function called Hero with two parameters: name and level. Since every character will have a name and a level, it makes sense for each new character to have these properties.

The this keyword will refer to the new instance that is created, so setting this.name to the name parameter ensures the new object will have a name property set.

The hierarchy looks like this:

The Prototype Object

Whenever we create a constructor function, JS will automatically create a literal object that acts as the prototype object.

That literal object’s sole purpose is to contain functions and properties for all instantiations of your constructor functions.

Note that this object (like all objects of JS) derives from Object, and will have its __proto__ pointing to Object’s Prototype Object.

This literal object acts as the prototype object for Hero. It will also have a constructor property pointing back to your constructor function. Thus, making this prototype object the same type as your constructor function.

In our case, we create a constructor function “Hero” with its properties. JS gives us an empty literal object (Let’s call it Prototype Object) in order to act as a single object that keeps properties and functions that are shared among all instances of Hero.

Note that our Hero Prototype Object derives from Object and thus, has its __proto__ referencing Object‘s Prototype Object.

It then creates and assigns a “constructor” property from the Hero Prototype Object to your Hero constructor function. This makes the prototype object type Hero. Finally, it assigns the “prototype” property from your Hero constructor property onto the “Hero Prototype Object”.

Creating an Instance

What this means is that any instantiations of Hero will have its OWN properties and functions from the Constructor function.
hero1 is an instance we just created. It has its own properties of name and level, as indicated from the constructor function Hero.

hence if we were to make three hero objects, hero1, hero2, and hero3, they all have their own unique properties of name, and level.
i.e

hero1 is “Grom”, level 1
hero2 is “Haomarush”, level 2
hero3 is “Thrall”, level 8

In addition, they ALL share the “Hero Prototype object”.

So if the “Hero Prototype object” has a property “nickname” initiated to “Orc”, then they would all have nickname = “Orc”.

Same goes for any functions and other properties that “Hero Prototype Object” may have.

Let’s check on how this works. First we display the prototype of our object hero1. You can do it in 2 ways:


output:

Hero {}
Hero {}

Now repeat with hero2, hero3. You’ll see that their __proto__ all are Hero{}. They all point to the same Prototype Object Hero{}

Adding to the Prototype

Let’s add a property nickname to the prototoype, and see what it looks like:

Now our “Hero Prototype Object” will look like this:

Hero { nickname: ‘orc’ }

This means that when we try to read a property from a Hero object, say from hero1,
– it will try to look for “nickname” in the hero1 instance first.
– When it does not find it, it will go up the hierarchy (i.e hero1.__proto__) into the prototype object, which it will then find.
– If it still does not find it, it will keep going up the prototype chain, hero1.__proto__, hero1.__proto__.__proto__….etc

If you were to change nickname:

You cannot change it from the instantiation

Our hierarchy is like this.

We have our Hero instance, with its own properties of name and level. It has its __proto__ pointing to an object, with property “type”, which is
initialized to orc. It has its constructor back to Hero, and __proto__ references pointing to Object’s Prototype Object.

Simply put, don’t confuse yourself when you try to change a property or function of a prototype object.

You cannot change it through an instantiation like this:

Erroneously, many people may think that it will see if nickname exist in the instantiation, it does not. Then it goes up to the prototype and finds it there. Then it just assigns the new string.

When it comes to setting values, JS will only let you access at the prototype level. It only let’s you read at the object level if the property does not exist at the object level.

If you do decide to assign at the object level, essentially Javascript will interpret it as you wanting to add a property called “type” to your instance, and then initialize it.

Thus, in the hierarchy, you’ll get two property called “type”. One is at the instance level. One is at the prototype level.

If you want to set the prototype object to a new value, you must do so through Object.prototype.YourPropertyName.

Different character classes of heroes

However, each hero has different abilities. Some wield different weapons. All have vastly different skills and abilities.

It wouldn’t make sense to put all the abilities for every class into the Hero constructor, because different classes will have different abilities.

We want to create new constructor functions, but we also want them to be connected to the original Hero.

Let’s take a look at how we can accomplish this with prototype inheritance and constructors.

The Warrior

We use call in order to chain constructors. Essentially, call replaces a function’s this reference to a passed in object.
Hence, we replace Hero’s constructor function’s this reference to point to Warrior’s this. Then, any kind of initialization done in Hero will take place in Warrior. In other words:

  • We first create a constructor function called Warrior. This identifies Warrior.
  • We then pass the “this” reference to Hero via function call. Using Warrior’s “this” reference, we start to construct it like a Hero. Essentially, we are doing chain constructors for Warrior. We want to first initialize Warrior to be a Hero with Hero’s properties and functions.
  • Then, we come back to Warrior, and initialize Warrior’s properties.

note: The JavaScript call() Method

The call() method is a predefined JavaScript function method.

It can be used to invoke (call) a function with an owner object as the first argument (parameter).
In our example, instance person calls fullName. However, fullName’s this is myObject.

With call(), you can use a method belonging to another object.

This example calls the fullName function of person, but is using it on myObject:

…back to our example…

In the extended object Warrior, we initialize Warrior with Hero’s properties via javascript’s call function.

So in the same way, we call Hero’s constructor, but with Warrior’s this.
We pass Warrior’s this into Hero’ constructor

Warrior total properties = Warrior own properties + Hero properties;

We’ll add the attack() method to Warrior because its what they do. We want all instances of Warrior to be able to attack, as they should.

Linking up the Prototype

So the warrior class has properties of the Hero class. But what about the Hero’s functionalities? Let’s say the Hero’s prototype has functionality to be able to run, jump, and swim.

The warrior, being a hero, should automatically have those functionalities as well.

From javascript standpoint, when you create Warriors, it will be setup in a similar fashion like Hero. You’ll get a Warrior Prototype Object. Your Warrior’s prototype reference will point to Warrior Prototype Object. Warrior Prototype Object will add and point “constructor” back to your Warrior.

Since Hero’s prototype properties and methods are not automatically linked when you use call() to chain constructors, our Warrior will use Object.create() to link the prototypes.

Now, given Hero’s hierarchy is like so:

In order to inherit Hero prototype
1) Warrior will first disregard its own prototype object
2) Use Object.create to clone its own Hero Prototype Object.
3) re-point Warrior’s prototype to this clone.

Note:
The Object.create() method creates a new object, using an existing object to provide the newly created object’s __proto__

Object.create


repoint Warrior’s prototype to our Hero Prototype Object clone

We simply want the type of all instances of Warrior to say Warrior {}.
It’s for clarity purposes.

We add an “attack function” to our custom Warrior Prototype Object.

We have class names’s prototype point to a literal object to denote that all
all instances of this class’s __proto__ should point to this literal object
for prototype functionalities. Since Warrior’s prototype now points to Hero Prototype object, this means all instances
of warriors have Hero Prototype Object’s prototype functionalities.

… given Hero Prototype functionality has jump, run, and swim, then our warrior will also be able to use these functionalities.

Now, all instances of Warrior can not only attack, but also jump, run, swim as well!

Double Prototype Level

Let’s create someone who has two types together.

First, we construct a Healer.

okay, so have Healer created. It derives from Hero in properties and prototype functionality.

double class power: Mage

Now let’s create a special class called Mage, where we can heal like a Healer, but also have our own Mage special ability.

Structure

You’ll see that an instance of the Mage will inherit properties and functions of its deriving object Warrior, and its deriving object Hero, and Object.

Furthermore, it will be able to use the whole hierarchy of its own prototype, which includes Warrior, Hero, and Object.


— Structure —

Mage {
name: ‘Thrall’,
level: 99,
weapon: ‘Ragnorak’,
special: ‘summons Bahamut’ }

Mage { constructor: [Function: Mage] }

Healer { constructor: [Function: Healer], heal: [Function] }

Hero { greet: [Function] }

{}

source code example