In this article, we will dive deep into the concept of prototypes: why JavaScript incorporates them and how we can harness their power. We will also learn about prototypal inheritance and how a prototype chain is created. Let's get started with a few highlighting points:
- In JavaScript, almost everything is an object.
- Each object is created using a constructor function, which has a prototype object.
- Every prototype object has a
constructor
method that refers to the constructor used to create the object. - Objects created using a built-in constructor, e.g.,
String
orArray
get a few predefined methods and properties in their prototypes. - Objects created using a custom constructor function get an empty prototype where we can add custom methods and properties.
- Methods and properties can be added to a prototype by attaching the
.prototype
keyword to a constructor function. - Methods and properties can be accessed from a prototype by attaching the
.__proto__
keyword to an object.
While everything in JavaScript is an object, there are technical names for different data types, e.g., string
, boolean
, etc. For the sake of consistency, we will refer to them all as objects in this article.
We will also refer to a prototype by the name of its constructor. For example, we will call it an Object
prototype when an object is created using the Object
constructor.
Prototype
A prototype is an object that contains methods and properties we can use to work with objects. Different prototypes contain different methods and properties. For example, the Object
prototype contains different methods and properties compared to those in the Array
prototype.
As you already know, a prototype is created when you create an object in JavaScript. Objects can be created in two ways: by using built-in constructors or by using custom constructor functions. Let's take a look.
Built-in constructor
const matt = {
age: 30,
occupation: 'software developer',
};
console.log(matt.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Object.prototype); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
In the above example, we've created an object called matt
using the built-in Object
constructor. As a result, matt
's prototype gets the methods and properties of the Object
prototype.
The Object
prototype is the base prototype in JavaScript. While all objects have their respective methods and properties within their prototypes, they also inherit methods and properties from the Object
prototype.
Good to know that while it's possible to add methods and properties to built-in prototypes, it's generally not recommended.
Custom constructor
Besides using the built-in constructors, we can use custom constructor functions to create objects in JavaScript.
function Person(age, occupation) {
this.age = age;
this.occupation = occupation;
}
const john = new Person(30, 'software developer');
console.log(Person.prototype); // {constructor: ƒ}
console.log(john.__proto__); // {constructor: ƒ}
In this code, we've created a constructor function called Person
and used it to create an object called john
. As per the rule, both the Person
function and the john
object get the same empty prototype object where we can add custom methods and properties.
Person.prototype.introduce = function () {
return `Hi, I'm a ${this.age} years old ${this.occupation}`;
};
console.log(john.__proto__); // {introduce: ƒ, constructor: ƒ}
In this code, we've added the introduce
method to the Person
constructor's prototype. So, john
and any other objects created using this constructor function can now access and utilize this method, as you can see in the following example.
console.log(john.introduce()); // Hi, I'm a 30 years old software developer
Prototypal inheritance
Prototypal inheritance is a mechanism that enables objects to inherit and utilize methods and properties from other prototypes. Let's explore this with an example.
Let's use the hasOwnProperty
method on john
, even though this method doesn't exist directly within the john
object or its own prototype:
console.log(john.hasOwnProperty('age')); // true
This works! But how? Here comes the prototypal inheritance into play.
As you know, every JavaScript object inherits methods and properties from the base Object
prototype, which contains the hasOwnProperty
method. Therefore, john
inherits the method and can utilize it.
This process of inheriting and utilizing methods and properties from other prototypes is known as prototypal inheritance.
Prototype chain
A prototype chain is a sequence of connections created when an object accesses methods and properties from other prototypes. When you use a method or property on an object that doesn't exist directly within the object or its own prototype, JavaScript searches for the method or property in the prototypes inherited by that object.
Consequently, a chain of connections is created between that object and its inherited prototypes, which is known as the prototype chain.
Why prototypes?
With prototypes, we can add methods and properties to a constructor function's prototype that other objects can inherit and utilize. This helps us write reusable code, resulting in increased performance and efficiency of our applications.
Without the prototypes, we would recreate the same methods and properties every time we create an object. This redundancy would result in inefficient memory usage and inferior performance.
This wraps up our discussion of the concept of prototypes, prototypal inheritance, and prototype chain in JavaScript. I hope this gives you a good understanding of how prototypes work, why they exist, and how we can utilize this JavaScript feature to build efficient and performant applications.