https://hackernoon.com/inheritance-in-javascript-21d2b82ffa6f
Example of chained prototype.
Hierarchy basics – Object
Firstly, all functions derive from “Object”, where “Object” is the default base of all javascript objects. The idea behind how hierarchy is laid out in JS is that the Object definition references an empty object called Object Prototype via a reference called “prototype”.
This Object Prototype object has a constructor reference pointing back to Object. Now all prototype objects of any function definitions you declare is an object literal. Since its an object literal, it will have a __proto__ reference, and this reference will be pointing to Object Prototype.
Example of custom Identification function
So let’s see how this works. Let’s declare a function definition called Identification. It automatically sets you up with a Identification object definition, and any related properties. In our case, we have a property called ‘name’, initialized to Ricky.
Furthermore, it will have a prototype reference to an empty literal object called “Identification Prototype”. This empty Identification Prototype will then have a constructor pointing back to our Identification object. More importantly, since this prototype object is a literal object, it will have a __proto__ reference pointing to Object Prototype.
If you were to simply create this code, and then evaluate the prototypes, constructors, and __proto__ references, you’ll see the output like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function Identification(firstName, lastName) { if (new.target !== undefined) { console.log("+ You've used new in constructing an [Identification] instance"); } this.firstName = firstName; this.lastName = lastName; this.friends = []; } // Identification.prototype // Identification {} // Identification.prototype.constructor // [Function: Identification] // Identification.prototype.__proto__ // {} // Identification.prototype.__proto__.constructor // [Function: Object] |
Identification.prototype references its own Identification Prototype object.
Identification.prototype –> Identification {}
The prototype object’s constructor references back to the Identification definition.
Identification.prototype.constructor –> [Function: Identification]
Since Identification.prototype is an object literal, it has a __proto__ and is pointing to Object Prototype.
Identification.prototype.__proto__ –> {}
Naturally, the constructor of the Object Prototype is a function of Object
Identification.prototype.__proto__.constructor –> [Function: Object]
Adding a function to Identification’s prototype
Appending any kind of functions to Identification prototype is straightforward. We are appending to the empty Identification Prototype object.
1 2 3 4 5 6 7 8 |
Identification.prototype.getName = function(){ console.log("Identification.prototype.getName: "); return this.firstName + ", " + this.lastName; } console.log("------- Identification.prototype -------"); console.log(Identification.prototype); console.log(Identification.prototype.constructor); |
When we analyze Identification Prototype, we’ll see that the function has been added to it. As expected the constructor is still on Identification.
Identification.prototype –> Identification { getName: [Function] }
Identification.prototype.constructor –> [Function: Identification]
Person basics
In the same way Identification was created, let’s create a Person object.
When the Person function definition is created, it will automatically have a prototype reference to an empty object literal called “Person Prototype”. Person Prototype will have a constructor reference back to Person. Naturally, Person Prototype will have __proto__ pointing to Object Prototype.
1 2 3 4 5 6 7 8 9 10 11 |
function Person(newAge, firstName, lastName){ if (new.target === undefined) { console.log('You didnt use new --> Giving you a [new Person]'); return new Person(); } else { console.log("+ You've used new in constructing an [Person] instance"); } this.first = firstName; this.last = lastName; this.age = newAge; } |
As expected we have:
Person.prototype.constructor –> [Function: Person]
Person.prototype –> Person {}
With Identification and Person definitions, we now have a hierarchy like this:
Now, I want Person to prototype information from Identification because naturally, each person have some sort of identificaion. Prototypes can only reference objects, thus, let’s create an instantiation of Identification. We’ll have first and last name to “ricky Tee”.
1 2 3 |
var identificationInstance = new Identification(); identificationInstance.firstName = "ricky"; identificationInstance.lastName = "Tee"; |
Remember, any kind of object literal (or instantiation) will have their __proto__ pointing to their own Prototype.
So in our case, since we made an instantiation of Identification, our instantiation’s __proto__ will reference Identification Prototype (along with any appended properties and functions)
identificationInstance.__proto__ –> Identification { getName: [Function] }
We then check what constructor our __proto__ is pointing to. Naturally, it is Identification.
identificationInstance.__proto__.constructor –> [Function: Identification]
Accessing the reference constructor gives the same result. The reason why is because when we try to access “constructor” on the instantiation object, it does not find it. So it goes up the hierarchy to its __proto__ and tries to find it there. __proto__ is Identification Prototype, and here we find constructor pointing to Identification.
identificationInstance.constructor –> [Function: Identification]
Chaining Person to Identification
Finally, we are set to chain one prototype to the next.
Specifically, since we have an Identification object to chain to, we assign Person’s prototype reference to it.
1 |
Person.prototype = identificationInstance; |
As you can see from the picture, Person’ prototype points AWAY from Person Prototype and onto the Identification instance.
Let’s check out Person’s prototype and constructor references now:
Person.prototype –> Identification { firstName: ‘ricky’, lastName: ‘Tee’, friends: [] }
Person.constructor –> [Function: Identification]
As you can see, prototype references Identification. The reason why is because Person.prototype points to the instantiation of Identification. It has its standard Identification properties as you can see from the output.
Next, we try to access constructor in Person. There is no constructor property in Person, so it goes to prototype in order to find constructor. Its prototype is the instantiation of Identification and does NOT have constructor either. It then goes up the hierarchy 1 more level at __proto__, which points to Identification Prototype. At this point, it sees that the constructor references Identification. Thus, that is why Person.constructor is Identification.
Solution
After understanding the basics above, its obvious that all instantiations of Person will share the same Identification prototype. That’s not good. In order to have each Person instantiation have their own Identification, the solution is to Inherit properties. We do this by having Person’s constructor call on Identification’s constructor using ‘this’. That way, Identification can start appending properties to Person’s ‘this’. In other words, we let Identification get Person’s this, then add properties to it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
function Identification(firstName, lastName) { if (new.target !== undefined) { console.log("+ You've used new in constructing an [Identification] instance"); } this.firstName = firstName; this.lastName = lastName; this.friends = []; } Identification.prototype.getName = function(){ console.log("Identification.prototype.getName: "); return this.firstName + ", " + this.lastName; } function Person(newAge, firstName, lastName){ if (new.target === undefined) { console.log('You didnt use new --> Giving you a [new Person]'); return new Person(); } else { console.log("+ You've used new in constructing an [Person] instance"); } //Inherit instance properties // this we pass 'this' into the Identification constructor. Thus, we're literally adding firstName, lastName, friends to our 'this' object. // in other words, we're literally passing Person into Identification so that Identification can take Person and append properties to it. //Identification constructor function is executed in the context of subTypeObj1 and add propeties firstName, lastName, friends to the subTypeObj1 object Identification.call(this, firstName, lastName); // After return of Identification.call(this, firstname, lastName), Identification constructor function adds a age property to the instance object. this.age = newAge; } //Inherit the properties from SuperType Person.prototype = new Identification(); //Add new property to SubType prototype Person.prototype.getAge = function(){ console.log("Person.prototype.getAge:"); return this.age; } console.log("\n\n--- start of program ---\n "); //Create SubType objects var rt = new Person(21, "Ricky", "T"); var ma = new Person(11, "Mohamed", "A"); console.log(rt.firstName); console.log(rt.age); console.log(rt.getName()); console.log(rt.getAge()); // friends was added to rt rt.friends.push("daniel", "fred"); console.log(ma.firstName); console.log(ma.age); console.log(ma.getName()); console.log(ma.getAge()); ma.friends.push("david b"); |