After reading the first two articles, "Prototypes" and "Inheritance," it's time to practice what we've learned. The example I came up with for this article is for learning purposes only. It's not related to real-world use cases.
Coming up with a good example turned out to be tricky. One reason is the difficulty of coming up with an example with meaningful logic and hierarchy. Hopefully, my solution will do the job.
The idea is as follows:
-
We need to create task items of some kind (like to-do lists). They could represent "li" elements, for example.
-
Now, let's assume there are two types of these items based on priority: primary and secondary.
-
Further, let's assume that all task items have the same parent element (ul) and CSS class. One way to provide this information to every task instance is through prototypal inheritance. Additionally, if we need to change the parent element or class name, every task item will detect the change.
-
We can see that one advantage of inheritance is that it shares code and makes it more flexible.
-
Our hierarchy for these examples looks like this:
First, we'll create the TaskParent() constructor. We'll assume that every final task item belongs to this parent, so we'll have information that every final item should be "aware" of. We'll put this information into the prototype object of the TaskParent() constructor. Why the prototype? Because this information is the same for every final instance, there is no need to clone it into every item instance.
function TaskParent() {}
TaskParent.prototype = {
parentElement: "ul",
parentID: "task-list",
getParentInfo: function () {
return `The parent of this task item is ${this.parentElement} element with id: ${this.parentID}`;
},
};Now, we're going to log TaskParent() to the console so we can inspect it and understand what has happened so far.
console.log(TaskParent)We see that the prototype object of the TaskParent() constructor contains the features that we have defined: parentElement, parentID and getParentInfo. As we know, every JavaScript object has an internal < prototype > object. Now, let's observe the internal < prototype > object of this prototype object.
We learned that the internal < protoype > object points to the object from which it directly inherits. In this case that object is the protoype object of the Object() constructor.
We should be able to understand yet another internal < protoype > object. The one of the TaskParent() constructor itself.
The TaskItem() will contain information that is the same for every task item. In this case, we will put that information into its prototype object.
function TaskItem() {}
// take care of inheritance
TaskItem.prototype = TaskParent.prototype;
TaskItem.prototype.constructor = TaskItem;
// augment the prototype with additional features
TaskItem.prototype.TaskItemElement = "li";
TaskItem.prototype.className = "task-item";
TaskItem.prototype.getItemInfo = function () {
return `This item is ${this.TaskItemElement} element with class: ${this.className}`;
};Now, the interesting part in the above code is that we actually inherited the prototype from the TaskParent() constructor without creating a new one for TaskItem(). Let's not forget that objects in JavaScript are copied by reference! This is more efficient way, because JavaScript engine will have less work to do when searching, for example, for getParentInfo() method. We'll see that later.
console.log(TaskItem.prototype === TaskParent.prototype) // trueLet's do another check:
console.log(TaskParent.prototype.hasOwnProperty("getParentInfo")); // true
console.log(TaskItem.prototype.hasOwnProperty("getParentInfo")); // trueWe've already mentioned that we'll have two types of task items, based on their priority. The primary and the secondary ones. For this purpose we'll create two constructors: ItemPrimary() and ItemSecondary(). The instances of ItemPrimary() will have priority property with value of 1 and the instances of ItemSecondary() will have priority with value of 2. Every task item, however, should inherit features from both the TaskParent() and the TaskItem(). Again, we'll only inherit the prototype object.
function ItemPrimary(title, description) {
this.title = title;
this.description = description;
this.priority = 1;
}
// take care of inheritance
ItemPrimary.prototype = TaskItem.prototype;
ItemPrimary.prototype.constructor = ItemPrimary;
function ItemSecondary(title, description) {
this.title = title;
this.description = description;
this.priority = 2;
}
// take care of inheritance
ItemSecondary.prototype = TaskItem.prototype;
ItemSecondary.prototype.constructor = ItemSecondary;const myFirstPrimaryTask = new ItemPrimary(
"title of the first primary task item",
"additional info for the first primary task item"
);
const myFirstSecondaryTask = new ItemSecondary(
"title of the first secondary task item",
"additional info for the first secondary task item"
)
console.log(myFirstPrimaryTask, myFirstSecondaryTask);We can easily recognize own properties of these two instances:
If we're going to call, let's say, getParentInfo() method, we'll see how easily JavaScript is going to find it because of inheriting a single prototype throughout our example. First, it will check direct properties. Then, it will recognize the internal < prototype > object and find the method. Only two steps involved in the process.
console.log(myFirstPrimaryTask)
console.log(myFirstPrimaryTask.getParentInfo()) // The parent of this task item is ul element with id: task-listMy first code for ItemPrimary() and ItemSecondary() constructors looked like this:
function ItemPrimary(title, description) {
this.title = title;
this.description = description;
}
// take care of inheritance
ItemPrimary.prototype = TaskItem.prototype;
ItemPrimary.prototype.constructor = ItemPrimary;
// augment the prototype with additional features
ItemPrimary.prototype.priority = 1;
function ItemSecondary(title, description) {
this.title = title;
this.description = description;
}
// take care of inheritance
ItemSecondary.prototype = TaskItem.prototype;
ItemSecondary.prototype.constructor = ItemSecondary;
// augment the prototype with additional features
ItemSecondary.prototype.priority = 2;What I didn't realize is that every instance created using either of these two constructors will actually have a priority property with value of 2. This happens because priority property at the end is overwritten and set to value of 2. And let's not forget that in this example we're actually working with only one prototype object. This is more efficient way of inheriting features, but we need to know what features should be inherited through this chain to avoid mistakes like this one.
The main goal of this article was to keep learning and to understand:
- prototype object of functions,
- internal < prototype > object that every object has
- inheritance process
This article is not about best practices or best possible solutions.
By no means am I a JavaScript expert, so feel free to comment and point to the potential mistakes. I write articles as my learning experience and with hope it can be useful for others. Any feedback is welcome.





