Did you know that everything in JavaScript is an object? You might wonder, "How can everything in JavaScript be an object?" This is possible thanks to prototypes. Don't worry, you'll learn about this in the article. Let's explore JavaScript's most important feature.
What is Prototype?
Prototype is a property of JavaScript which help objects to inherit property or methods from one another.
Let’s take an example,
const person = {
name: "Viewer",
greet: function() { console.log("Hello, I am Viewer") }
}
We create an object person
and give it a property name
and a method greet
.
Go to console and try to console log the object person
. We get this :
In this, we get the property name
and method greet
. But we also get a method called [[Prototype]]: Object
. What’s this?
This is an object called Prototype
. So, the prototype we are discussing is an object. So, if it's an object, it should have its own properties too, right?
Yes, it has many properties (methods) like toString()
, valueOf()
, etc. Let’s try to console log some of these properties.
console.log(person.toString)
But, we don’t have a method called toString() in the person
object. Will it even work? Let’s find out
It’s giving as [object Object]
. So, we can call the toString()
method even when we don’t define some methods in person
object. Why? This is because of prototype chaining.
Prototype Chaining
According to prototype chaining, we can call a method that is not in the object itself, but if it exists in its prototype, it will return a value. If the method is not in the object's prototype, it will check the prototype's prototype. If it finds the method there, it will return the value; otherwise, it will return null or undefined
.
Let’s take an example,
const person = {
greet: function() { console.log("Hello") }
}
In this code, we created an object person
and defined a method greet
.
We know that we can access some properties of prototype of person
which are defined in person
object using [[Prototype]] linkage. Let’s understand using diagram;
According to this diagram, we can see that the person's prototype is linked to the Object's prototype through [[Prototype]]
linkage. We also notice that there is a constructor we didn't define, and through this constructor, we are connected to the person, and the same applies to the Object.
A constructor is a function that points to its object, and if we log the object to the console, its value will be an object. You can also see this in the image above, which displays all the methods of the prototype.
We can also see that in diagram, person is connected to its methods through Prototype. In backend of JavaScript, when we create anything like object, it connect it’s property through objectName.Prototype
.
Shadowing
What happens if you define a property in an object, when a property with the same name is defined in the object's prototype? Let's see:
const person = {
name: "Viewer",
toString: () => { console.log("This is toString() method") }
}
In this code, we created an object person
and defined property name
and a method toString()
.
Let’s call person’s method toString()
.
person.toString()
// output : This is toString() method
You might be wondering, "Can we create a method with the same name as the Object's prototype?" The answer is yes, and it will return the value that you define in it.
This happens because JavaScript first checks if the method we are calling exists in the object itself. If it doesn't, JavaScript then looks in the object's prototype. In this case, since the method is in the object, it returns the value we defined.
Prototypal Inheritance
If you've worked with other object-oriented programming languages like C++ or Java, you might already know about inheritance.
In this programming style, we create a class, which acts as a blueprint for an object. If you want to create a new class that includes all the properties of an existing class, you can extend the new class from the existing one. This is known as Classical Inheritance.
But in JavaScript, we use prototypal inheritance instead of classical inheritance.
In prototypal inheritance, object inherit the properties of another object via prototype linkage.
Lets take an example to understand this,
const person = {
name: "Viewer",
greet: () => { console.log("Hello") }
}
In this code, we created an object person
which have property name
and method greet
.
By default, the JavaScript engine provides you with a built-in Object()
function and an anonymous object that can be referenced by the Object.prototype
:
In this circle represents function and square represents object.
The person object has a link to the anonymous object referenced by the Object()
function. The [[Prototype]]
represents the linkage:
This means that person
object can call the methods of Object which is not defined in it. We have already seen it.
We can access the prototype of person object, using __proto__
property. Lets try this :
console.log(person.__proto__)
Note: We should never use __proto__ property in the production code.
The following code shows that person.__proto__
and Object.prototype
references to same object.
console.log(person.__proto__ == Object.prototype) // true
Let’s create a new object teacher
which have a method teach()
.
const teacher = {
teach: (subject) => {
console.log(`I teach ${subject} subject`)
}
}
Like person
object, this teacher.__proto__
also points to Object Prototype.
If you want teacher object to get the access of all the property of person object, you can use __proto__
to set the prototype of teacher
to person
.
teacher.__proto__ = person
Now, teacher
object can access property of person
via [[Prototype]]
.
console.log(teacher.name) // Viewer
console.log(teacher.greet()) // Hello
We can call that teacher
inherited the property of person
. This is called Prototypal Inheritance.
A Standard Way to Implement Prototypal Inheritance in ES5
ES5 provided a standard way to work with prototypal inheritance by using the Object.create()
method.
Note that now you should use the newer ES6 class
and extends
keywords to implement inheritance. It’s much simpler.
The Object.create()
method creates a new object and uses an existing object as a prototype of the new object:
Object.create(proto, [propertiesObject])
The Object.create()
method accepts two arguments:
The first argument (
proto
) is an object used as the prototype for the new object.The second argument (
propertiesObject
), if provided, is an optional object that defines additional properties for the new object.
Suppose you have a person
object:
let person = {
name: "Viewer",
greet: function () {
return "Hi, I'm " + this.name;
}
};
The following creates an empty teacher
object with the __proto__
of the person
object:
let teacher = Object.create(person);
After that, you can define properties for the teacher
object:
teacher.name = 'Jane Doe';
teacher.teach = function (subject) {
return "I can teach " + subject;
}
Or you can do all of these steps in one statement as follows:
let teacher = Object.create(person, {
name: { value: 'Viewer' } ,
teach: { value: function(subject) {
return "I can teach " + subject;
}}
});
ES5 also introduced the Object.getPrototypeOf()
method that returns the prototype of an object. For example:
console.log(Object.getPrototypeOf(teacher) === person);
Output:
true
Defining methods in the JavaScript prototype object
We can define our own methods in the prototype of Object or any other prototype linked to our object. Let's use the earlier example of person
and teacher
. Here, teacher
inherits the properties of person
. We want to create a new method in person
that returns the full name.
teacher.prototype.fullName = () => this.name
In this case, JS engine add new method fullName()
in prototype of teacher
which is person
.
Why everything in JavaScript is Object?
After all this, you might have figured out why everything is an object. For those of us who aren't sure, let's look at an example.,
const num = [1,2,3]
This is an array num
. Let's log num.__proto__
in the console. The output will be
This will print all the methods of the array. But if you look at the bottom, you will see [[Prototype]]: Object
. And if you log num.__proto__.__proto__
again, you will see this :
Haven’t we seen it before. That’s right, this is Object.Prototype
.
If you take any other data structure, like a string or date, and try to log its __proto__'s __proto__, then you will see that, in the end, it's an object.
Conclusion
In conclusion, understanding JavaScript prototypes is crucial for grasping how inheritance and object properties work in the language. Prototypes allow objects to inherit properties and methods from other objects, enabling efficient code reuse and organization. By leveraging prototype chaining, shadowing, and prototypal inheritance, developers can create complex and dynamic applications. Recognizing that everything in JavaScript is ultimately an object helps in understanding the language's flexibility and power.