Prototypes and Classes in JavaScript
In this blog we will understand prototypes, __proto__ property, "new" keyword, and classes in javascript.
Prerequisite
This blog assumes that you’re somewhat familiar with the objects
and this
keyword in javascript. If not, then please go through them once.
Introduction
Let's start with objects, so suppose we are building a game where each user object has a username, score, and increment method. So one way to create this object is to have a function that takes all the data creates a new object and returns that object.
It will look like this:
function userCreator(name) {
const userObject = {}
userObject.score = 0,
userObject.username = name,
userObject.incrementScore = function() {
this.score++
}
return userObject
}
const user = userCreator('john')
console.log(user) // {score: 0, username: 'john', incrementScore : ƒ}
This is okay for one or two users, but what about if we have N number of users, this won't be an efficient way to create a new user object, because if you see userCreator
function carefully every time it creates a new object with that it also creates a new incrementScore
method which is same for all the objects.
So rather than creating a new incrementScore
again and again for each object, can't we store it in one place where all the objects created using userCreator
function can refer to.
Prototype chain
Using prototype chaining we can solve the above problem let's see. But before that let's dig into objects a little more.
In global memory, there is an Object constructor
which is stored as a function + object representation.
Now all the functions in their object representation have the prototype
property which itself is an object.
All the objects in javascript have a __proto__
property, which by default has a reference or link or chain to that main window.Object.prototype
which is present in global.
So now let's come back to userCreator
function, but this time will create the objects using Object.create().
Object.create() creates a new object using other object as its prototype.
So Object.create()
takes null
or another object, what it does is when an object is created using this method, that object's __proto__
which was by default pointing to the main window.Object
kept in global, now will no longer point to that instead it will point to the object which we provided as an argument to Object.create()
.
KEY POINT: The argument given to the
Object.create()
is itself is an object whose__proto__
will point to thewindow.Object
which is kept in global.
If we give null
to the Object.create()
as an argument then the object which is created, its __proto__
will no longer have any reference to the window.Object
or any other objects.
Let's see an example when we give an object as a argument to the Object.create
for userCreator
fucntion:
const commonFunctionsObject = {
increment: function() {
this.score++
}
}
function userCreator(name) {
const userObject = Object.create(commonFunctionsObject)
userObject.score = 0
userObject.username = name
return userObject
}
const user = userCreator('john')
console.log(user) // {score: 0, username: 'john'}
But if we see the __proto__
of the user
object then we can see that reference.
KEY POINT: So now if you see
user
object carefully then it shows[[Prototype]]
where ourcommonFunctionsObject
was stored. So the[[Prototype]]
property is how the__proto__
link is indicated in javascript. And[[ ]]
aroundPrototype
indicates that it's an internal property that we can't access directly viauser.Prototype
You can also see that there are two [[Prototype]]
, so what's that second one?
You already know now, as I said commonFunctionsObject
is an object whose __proto__
has a link to the main window.Object
which is in global memory.
user -> commonFunctionsObject -> window.Object
So if we try to access the increment
method on user
then first it will look into itself, but it won't find it as it's not there only.
So javascript won't throw an error instead now it will go to its __proto__
link and check that object, in this case, it's commonFunctionsObject
over there it will find the increment
method and will execute it.
The lookup will look like user
then commonFunctionsObject
, and this is known as a Prototype chain
, the chain which the object will follow to look for any property on it.
"new" keyword
What we see above, the way of creating objects using Object.create
and then making there __proto__
point to the object that we pass as an argument to Object.create
and then returning the newly created object is what new
keyword automates for us.
Let's see an example:
function UserCreator(name, score) {
this.score = score
this.username = name
}
const user = new UserCreator("john", 0)
console.log(user) // {score: 0, username: 'john'}
Now, what is this UserCreator
, and how it is different from userCreator
from the previous example?, so the first thing you see is the name UserCreator
is basically a convention to name the function in capitalize way which indicates to developers that this function is used to create objects using new
keyword.
UserCreator
function is also called as constructor function
.
So when we do const user = new UserCreator("john", 0)
the UserCreator
function will run:
- It first creates an empty object, and there is
this
which is by default present in the memory which will refer to the newly created empty object in the function's memory. - Assigns all the values in the object that we pass and return the object.
- All the objects which are created now will have their
__proto__
link to theUserCreator.prototype
.
Now if we want to put our shared functions in this case increment
, we can put them inside UserCreator.prototype
.
function UserCreator(name, score) {
this.score = score
this.username = name
}
UserCreator.prototype.increment = function() {
this.score++
}
const user = new UserCreator("john", 0)
If we create N users then all will refer to this same increment function.
Class
Classes were introduced in ECMAScript 2015 or ES6, now this is not a new thing, it's just syntactic sugar over what we just learned above for new
keyword we can say a wrapper around that.
class UserCreator {
constructor(name, score) {
this.score = score
this.username = name
}
increment() {
this.score++
}
}
const user = new UserCreator("john", 0)
console.log(user) // {score: 0, username: 'john'}
Now we write our shared functions inside the wrapper itself i.e class
and those functions will be stored in the UserCreator.prototype
automatically.
So this is what all the class
does.
Summary
- All objects in javascript have the
__proto__
property which by default has a link or a chain towindow.Object.prototype
. - Functions are stored as a function + its object representation in memory and all function's object representation have a default property called
prototype
. new
keyword automates the object creation and linking of their__proto__
.class
is just a syntactic sugar / a wrapper. Rather than writing the shared functions in the constructor's prototype we directly write them inside the class which automatically stores is in the constructor's prototype.
References
- developer.mozilla.org/en-US/docs/Web/JavaSc..
- developer.mozilla.org/en-US/docs/Web/JavaSc..
- frontendmasters.com/courses/?q=javascript
That's it folks I hope you were now able to understand all this.