Introduction
JavaScript is one of the most popular programming languages. If you know anything about computer programming, you have probably heard of JavaScript. If you dig into a little more detail, you will discover that JavaScript is a prototype-based language. This means that you can use general objects to share object properties and methods. You can clone or even extend these objects as you need.
As opposed to object-based programming languages, you have class-based languages. Here, you will have to define a blueprint for your objects instead. These languages include the likes of Java, Python, and PHP.
However, in this tutorial, we will be focusing on prototype and object-oriented programming languages like JavaScript. We will discuss what object prototypes are and how you can extend them with the constructor. We will also talk about JavaScript inheritances in some detail. Let’s begin!
Prototypes in JavaScript
If you want to learn more about objects in JavaScript, in particular, you can refer to this source. We shall assume that you already know what an object data type is and how you can change its properties. This section will show you how to extend your objects.
To start with, you should know about Prototype
. This is the internal property that is associated with all JavaScript objects. You can visualize this by making an empty object like this:
1 |
let x = {}; |
There is another way for you to create such an object. Let’s say we want to use the constructor function instead. This time, you would use the object constructor:
1 |
let x = new Object() |
Now we shall see how you can attain the[[Prototype]]
of your new object. For this purpose, you will use the getPrototypeOf()
method:
1 |
Object.getPrototypeOf(x); |
1 2 3 |
Output {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …} |
The output shows you a number of properties and methods. An alternative way to get the [[Prototype]]
is by using the property as it exposes the internal __proto__
of any object:
1 |
x.__proto__; |
1 2 3 |
Output {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …} |
As you can see, the output is the same as the previous method. The reason why [[Prototype]]
is important is that it allows you to link objects. Newly created objects, as well as built-in objects, have a [[Prototype]]
.
What is Prototype Inheritance?
There is a sequence of events that happens when you search for the property or method of an object. First, the program will search the object. Then, it will search the [[Prototype]]
of that object. If it is not found still, the program will check the prototype of the linked object. This way, it will go through the prototype chain until it finds what you asked for.
The last thing on the end of the prototype chain is something called the Object.prototype
. You can read more about it from the official source. Remember that if you try to search beyond this point, you will get a null
result. All of the objects will inherit the same properties and methods as the object itself.
To understand this better, here is an example. We will take x
as the empty object from the previous section. It will be inheriting from Object
. This means that it can use all the properties and methods of Object
. An example of this would be toString()
:
1 |
x.toString(); |
Output:
Think about the prototype chain in this case. It only contains one link, going from x
to Object
. You can confirm this by trying to chain the [[Prototype]]
properties together like this:
1 |
x.__proto__.__proto__; |
Output:
Your output will show null as a result.
Next, we will look at built-in objects and methods like pop()
and push()
. If you make a new array, you will have access to these built-in methods. This is because your array has access to all the properties and methods on Array.prototype
. In order to test this out, make a new array like so:
1 |
let y = []; |
Like we talked before, you can make a new array using the constructor let y = new Array()
as well. Now, we shall explore the [[Prototype]]
of the new y
array:
1 |
y.__proto__; |
1 |
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …] |
The output shows more properties and methods than the x
object in our last section. This is because it inherited data from Array.prototype
. Another thing you may notice is the constructor
property set to Array()
. You can use this to make objects from functions.
Now, that our chain is longer, we shall demonstrate how to chain two prototypes together:
1 |
y.__proto__.__proto__; |
1 2 3 |
Output {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …} |
Currently, your chain refers to Object.prototype
. Let’s check if they all refer to the same thing:
1 2 3 |
y.__proto__ === Array.prototype; // true y.__proto__.__proto__ === Object.prototype; // true |
You can also achieve the same effect using the isPrototypeOf()
method:
1 2 3 |
Array.prototype.isPrototypeOf(y); // true Object.prototype.isPrototypeOf(Array); // true |
You should further know about the instanceof
operator. This operator lets you test if the prototype
property of a constructor appears in the prototype chain of a given object:
1 |
y instanceof Array; // true |
How to Use Constructor Functions?
Next, you will learn about constructor functions. These help you construct new objects as the name suggests. For example, the new
operator lets you create new instances which are based on the constructor function. Constructor functions are just like normal functions. You only have to add new
at the beginning. Whereas you have built-in constructors like new Array()
and new Date()
, you can also make custom templates. In our example, this is exactly what we will do.
We will be making a very simple role-playing game that is only text-based. We want the user to be able to choose a character and the character class. The characteristics of the characters, like name and level, will be common so we would like to make a constructor as a template. However, the character abilities will be unique. Thus, we want the character to only access its own abilities. You will see how we will use prototype inheritance and constructors for this purpose:
1 2 3 4 5 6 7 8 9 |
// Initialize a constructor function for a new Hero function Hero(name, level) { this.name = name; this.level = level; } |
As per this code, we made a constructor function Hero
with the parameters of name
and level
. The this
keyword helps us refer to the current instance. Let’s make a brand new instance:
1 |
let hero1 = new Hero('Thor', 9); |
Consoling out hero1
will show you a new object with new properties:
If you want to see the constructor
as Hero()
, all you need to do is acquire the [[Prototype]]
of hero1
:
1 |
Object.getPrototypeOf(hero1); |
1 2 3 |
Output constructor: ƒ Hero(name, level) |
Typically, JavaScript users define methods on the prototype instead of the constructor. This is because this code is easier to read and makes the process more efficient. You can add a method to Hero
with the help of prototype
. Let’s create a greet()
method:
1 2 3 4 5 6 7 8 9 |
... // Add greet method to the Hero prototype Hero.prototype.greet = function () { return `${this.name} says hello.`; } |
This method will be available to hero1
:
1 |
hero1.greet(); |
Output:
You can see greet()
as an option, if you inspect [[Prototype]]
.
Next in our scenario, we want to make character classes that the heroes can use. Different classes will have different abilities. That is why we will make new constructor functions that connect back to the original Hero
. The constructors we make will be Healer and Warrior. To copy the properties constructor to constructor, we will use the call()
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
... // Initialize Warrior constructor function Warrior(name, level, weapon) { // Chain constructor with call Hero.call(this, name, level); // Add a new property this.weapon = weapon; } // Initialize Healer constructor function Healer(name, level, spell) { Hero.call(this, name, level); this.spell = spell; } |
We will add some unique properties like attack()
for Warrior and heal()
for Healer:
1 2 3 4 5 6 7 8 |
... Warrior.prototype.attack = function () { return `${this.name} attacks with the ${this.weapon}.`; } Healer.prototype.heal = function () { return `${this.name} casts ${this.spell}.`; } |
Now, your characters will have two new available classes:
1 2 |
const hero1 = new Warrior('Thor', 1, 'hammer'); const hero2 = new Healer('Loki', 1, 'spear'); |
As per this programming, hero1
is a Warrior and will have the relevant properties:
1 2 |
Output Warrior {name: "Bjorn", level: 1, weapon: "axe"} |
Next, we shall try using the new methods on the Warrior prototype:
1 |
hero1.attack(); |
Console:
Here is an attempt at using methods down the prototype chain:
1 |
hero1.greet(); |
Output:
Using the call()
method on chain constructors does not link the prototype properties and methods. Instead, you can use Object.create()
:
1 2 3 4 5 6 7 8 |
... Warrior.prototype = Object.create(Hero.prototype); Healer.prototype = Object.create(Hero.prototype); // All other prototype methods added below ... |
Next, you will be able to use the prototype methods from Hero
on a Warrior
or Healer
:
1 |
hero1.greet(); |
Finally, you can see all of our programming for this game:
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 |
// Initialize constructor functions function Hero(name, level) { this.name = name; this.level = level; } function Warrior(name, level, weapon) { Hero.call(this, name, level); this.weapon = weapon; } function Healer(name, level, spell) { Hero.call(this, name, level); this.spell = spell; } // Link prototypes and add prototype methods Warrior.prototype = Object.create(Hero.prototype); Healer.prototype = Object.create(Hero.prototype); Hero.prototype.greet = function () { return `${this.name} says hello.`; } Warrior.prototype.attack = function () { return `${this.name} attacks with the ${this.weapon}.`; } Healer.prototype.heal = function () { return `${this.name} casts ${this.spell}.`; } // Initialize individual character instances const hero1 = new Warrior('Bjorn', 1, 'axe'); const hero2 = new Healer('Kanin', 1, 'cure'); |
As per this code, we have a class with certain common properties, two character classes, and unique character instances for Warrior and Healer each.
Conclusion
JavaScript is an extremely versatile programming language based on objects and prototypes. This makes it quite different from other class-based coding languages. However, it also gives you a lot of unique features and opportunities to capitalize on. We discussed a few of those throughout this tutorial.
We explored prototypes in great detail. Then, we went over object properties, how to link them, inheritances, as well as constructor functions. Now you can effectively manipulate property and method values across different objects. You can read more about working with objects from the official source as well.
Here are more resources from our blog that will help you further utilize JavaScript:
- In this tutorial, we explore in detail how you can add JavaScript to HTML.
- If you are building your own web application, take a look at our guide on how to choose the best server setup.
- If you want to learn how to set up a blog with Ghost, take a look at this tutorial.
Happy Computing!
- Removing Spaces in Python - March 24, 2023
- Is Kubernetes Right for Me? Choosing the Best Deployment Platform for your Business - March 10, 2023
- Cloud Provider of tomorrow - March 6, 2023
- SOLID: The First 5 Principles of Object-Oriented Design? - March 3, 2023
- Setting Up CSS and HTML for Your Website: A Tutorial - October 28, 2022