In object-oriented programming, we can distinguish between two types of languages. Class-based and prototype-based languages.
Class-based languages are centered around classes as the blueprint for creating objects. However, in prototype-based language classes are explicitly excluded, and objects inherit from other objects using prototypes.
Compared to class-based languages like C++ and Java, JavaScript is considered a Prototype-based object-oriented programming language.
This article delves deep into understanding prototype-based object-oriented programming in JavaScript.
By the end of this article you should be able to know:
The differences between class-based and prototype-based OOP
How inheritance works in class-based programming
How inheritance works in prototype-based programming
Prototypes and
__proto__propertiesHow to implement prototypal inheritance
Let's get started
Introduction
Whiles working at Netscape, Brendan Eich developed JavaScript as a scripting language to be used in Netscape Navigator, the company's flagship web browser.
Netscape had initially partnered with Sun to incorporate Java programming language into the Navigator. However, the language had some complexities (for instance the JVM consumes a lot of resources, it took several seconds to start a Java applet, etc) that would not appeal to scripters who need to add interactivity to web pages.
To rectify this, Brendan Eich developed JavaScript as a utility language targeted toward scripters and designers.
Netscape's management instructed Brendan Eich not to include advanced features such as classes and modules in JavaScript, to prevent it from appearing to compete with Java.
Because Brendan was not allowed to include classes (which provides a convenient approach for creating objects and inheriting properties and methods), he had to find a way to still maintain the Object-oriented model in JavaScript. He opted for prototypes and implemented prototypal inheritance instead.
Javascript didn't adopt the object-oriented model used by C++ or Java when it was designed because the creator had no time to copy any classical OO model, and Sun didn't want classes included in JS, since JS was supposed to be just a 'sidekick' to Java.
JavaScript is therefore a prototype-based, object-oriented language and uses prototypal inheritance instead of classical inheritance.
Simply put, JavaScript does not have classes, it has prototype objects cloned to produce new objects. Classes are only syntactic sugar for prototypes.
Class-based Programming language
In Class-based programming, objects are built based on the classes. The properties and behavior of an object are defined by a class that serves as a blueprint to create concrete objects.
Consider this analogy, if you were to develop a mobile phone device, you would first design a blueprint that contains all the structure and behavior needed to build the mobile phone. For instance the exterior, operating system, circuit boards, etc, and then build the phone based on that blueprint.
This blueprint is referred to as a class . It is an expandable program code template for creating objects. An object is a real-world entity (for instance a mobile phone).
The blueprint (class) can be extended to build other variations of mobile phone devices ( basic phones, feature phones, and smartphones)
Simply put, in class-based programming, an object must be explicitly created based on a class.
Prototype-based Programming language
Prototype-based programming is a style of object-oriented programming in which classes are not explicitly defined. Inheritance is performed by reusing existing objects that serve as prototypes (objects inherit from other objects through a prototype property).
It permits the creation of an object without first defining its class.
Consider the same analogy of developing a mobile phone device in prototype-based programming.
You first begin by creating the objects ( no blueprints or classes). You assemble the exterior, operating system, circuit boards, etc, and spin up a mobile phone. We can then clone the initial mobile phone and then extend the structure and behavior for the required mobile device (for instance, a smartphone with a wider screen size, touch functionality, sensors, etc).
Using this approach, each mobile phone would be cloned from the generic mobile phone object.
In the world of prototype-based OOP, you build an object and swiftly create "clones" of it compared to the class-based approach, which requires a class to create an object, and then extend the child class to inherit properties and methods from the parent.
Understanding the concept of Prototype-based OOP
In class-based OOP, a class serves as a blueprint for creating objects. A child class will inherit the properties and methods defined in the parent class , and distinct objects are created using the constructor function.
However, in prototype-based OOP, we do not need to define a class to enable us to create object. Objects are created directly.
Because we don't define classes, we need an approach that will enable other objects to inherit properties and methods from the initial object.
There is a hidden property in every object called prototype that enables us to achieve that.
Let's explore this further in the next section.
What is a Prototype?
A prototype is a built-in (hidden) property represented by [[Prototype]] that points to another object. The value pointed at by the [[Prototype]] is casually known as "the prototype of that object"
Consider the example below:
//user object
const user = {
name: "Emmanuel",
age: 23,
city: "Accra"
}
console.log(user)
In the console, you can view the properties of user, alongside the [[Prototype]] (hidden property).
Because the value of the [[Prototype]] is an Object, we say, it is the prototype of the user object. The Object will also have a prototype property (represented as __proto__ ) including other built-in methods.
This __proto__ is not the same as the hidden [[Prototype]]. It allows us to read and assign details to the [[Prototype]]
Let's expand the Object to investigate
Prototype chain
Whenever the __proto__ property points to an object . That object will also have a prototype that will also point to another object. In effect, it creates a prototype chain. The prototype chain ends with an object that has a value of null
How prototype works
Consider the user object below.
It has three properties: name, age and city.
const user = {
name: "Emmanuel",
age: 23,
city: "Accra"
}
We can access these properties, via the dot (obj.propName ) or bracket (obj['propName']) .
Let's access the name property
user.name
// which returns
Emmanuel
We instantly have access to the value(Emmanuel)
Now, what happens if we access a property that does not exist in an object?
If we access a property and the object does not directly have that property, the JS engine will look for a property with that name by first searching the [[Prototype]], and because it references an object, it looks for the property in that object.
If it cannot locate the property, that object [[prototype]] will be searched. This search will continue in turns until it a match is found, or reaches the end of the prototype chain.
At the top of the prototype chain is null, at this stage, the property was not found, and undefined is returned.
The code below illustrates the prototype chain
let person = {
eats: true,
hasLegs: 2,
walks(){ console.log('I can walk')}
}
//define another object
let man = {
hasBreast: false,
hasBeard : true,
}
//set the prototype of man to person object
man.__proto__ = person;
//define a third object
let samuel = {
age: 23
}
//set the prototype of samuel to man
samuel.__proto__ = man;
//access walk method from samuel
console.log(samuel.walks())
//access hasBeard from samuel
console.log(samuel.hasBeard)
We define a
personobject witheatsandhasLegsproperties, including awalksmethodWe create a
manobject and set the prototype of man to thepersonobject usingman.__proto__= personWe create a specific man
samueland set the prototype ofsamueltomanusingsamuel.__proto__ = manNow, we try accessing the
samuel.walks()andsamuel.hasBeardIt is clear from thesamuelobject that there is nowalkmethod and ahasBeardproperty.
How then, do we get the output I can walk and true ?
Whenever
samuel.walks()is called, the JavaScript engine will first search the objectsamuelfor the method.If it is not found, the
[[Prototype]]ofsamuelwill be searched.The
[[Prototype]]has been set to the objectman, hence the JavaScript engine checks themanfor thewalkmethod.If it is not found, the
[[Prototype]]ofmanwill be searched.Its
[[Prototype]]has been set to theperson, so we check thepersonfor thewalkmethod.Finally, at the top of the prototype chain, we can locate the method
walkin the objectpersonand the JavaScript calls the method even though it wasn't defined in the objectsamuel.
Let's consider another example. Supposing we want to convert user.name to upper case, we can do it as below:
//convert from Emmanuel to EMMANUEL
const user = {
name: "Emmanuel",
age: 23,
city: "Accra"
}
console.log(user.name.toUpperCase())
When we call the method toUppercase()
The JavaScript engine looks for the
toUpperCasein theuserobjectIf it can't locate it there, it searches the
[[prototype]]property ofuserfortoUpperCaseFinds it there, and executes the method.
Dimming Properties
Whenever you define a property in an object, and that property has the same name as a property in the [[prototype]], the object's prototype is dimmed and that of the defined property takes precedence.
Consider this example
const myDate = new Date(1999,05,26)
console.log(myDate.getFullYear()) // 1999
//define a method for myDate
myDate.getFullYear = function(){
console.log('changed the default getFullYear method')
}
myDate.getFullYear()
getFullYear()is a built-in method on themyDateobject. When executed, it returns the full yearWe assign a property with the same name to the
myDateobject, the value of the property is a function that consoleschanged the default getFullYear methodThis approach overwrites the built-in method, this is because the JS engines first check the
myDateobject for thegetFullYearproperty. Since it finds it there, there is no need to check the object's prototype
This is called "shadowing" the property
Setting the prototype on an object
There two main approaches in setting an object's prototype are:
Object.create()
Constructor
Let's explore this further.
Using Object.create() to set the prototype
The Object.create() creates a new object and allows you to use existing objects as the new object's prototype.
The syntax is as below:
Object.create(proto) // proto is the existing object
See the example below:
const user = {
isLoggedIn: false,
greetUser(){
console.log(`Howdy ${this.name} !`)
}
}
//create a new object and set the prototype
const admin = Object.create(user)
We defined a
userobject, withisLoggedInproperty andgreetUsermethod.The
Object.create()creates a newadminobject.We set the prototype of the
adminobject to theuserusing theObject.create(user)
Below is the result of logging the admin to the console
Let's examine the code above:
When we first investigate the details of the
adminobject, it looks empty. However, we notice there exists a[[Prototype]]property. This property is in-built and references the prototype of theadminobject.We conclude that the prototype has been set to the existing
userobject.
We can also assign properties to the admin object. See the example below:
const user = {
isLoggedIn: false,
greetUser(){
console.log(`Howdy ${this.name} !`)
}
}
//create an object an set the prototype
const admin = Object.create(user)
console.log(admin)
//assign name property to the admin object
admin.name = "Emmanuel"
The admin now has name property assigned to it. This name property is set on admin but not on user.
Now that we've set the prototype of the object, let's try accessing the greetUser() that was defined in the user object from the admin.
const user = {
isLoggedIn: false,
greetUser(){
console.log(`Howdy ${this.name} !`)
}
}
//create an object an set the prototype
const admin = Object.create(user)
console.log(admin)
//assign name property to the admin object
admin.name = "Emmanuel"
//access greetUser from admin
admin.greetUser()
// output
Howdy Emmanuel !
The output is as below:
How was it possible to access, the greetUser method when it was not defined in the admn object?
Let's examine this further
nameis the only visible property in theadminobjectThere is also a hidden
[[Prototype]]property, that reference anObjectThat
Objectis the existinguserwe passed to theObject.create()method.As explained, when we access a property on an object, and that property does not exist, we will look at the prototype of the object for the
property.In this scenario, the prototype of the object
adminhas been set touser.In the
userprototype, we have access to thegreetUsermethodNow we can call
greetUser()on theadmin, and the prototype will handle the implementation.This explains why, the
admin.greetUser()executes the method even though it was not defined in theadmin.Note that, the
thiskeyword used in theuserwill reference the newly created object ( in this exampleadmin). Hence,this.namereferencesEmmanuel
Using the constructor function to set the prototype
In JavaScript, every function has a prototype property. The prototype enables us to add properties and methods to a constructor function.
Whenever we create an object from the constructor function, the object can inherit the properties and methods from the function's prototype.
Here is an example
//constructor function
function User (name) {
this.name =name,
this.age = 34
}
//add greet method to the function's prototype
User.prototype.greet = function(){console.log(`Howdy ${this.name}`)}
//create a new object
const admin = new User("Jonas");
//access the greet method from the admin
console.log(admin.greet()) // Howdy Jonas
We define a constructor function
User, withnameandagepropertiesThe
Userhas aprototypeproperty that allows us to assign thegreetmethod.We created an
adminobject from theUserNow, even though
greethas not been defined in theadmin, it inherits that method from the prototype.This explains why we can call the
greetmethod on theadminobject.
New objects created from the constructor function will also inherit the greet method as well as additional properties and methods assigned to the prototype.
Furthermore, if a prototype value is changed, all the new objects will have the updated value.
See the example below:
function User (name) {
this.name =name,
this.age = 34
}
//create a new object
const admin = new User("Jonas");
//add greet method to the constructor function
User.prototype.greet = function(){console.log(`Howdy ${this.name}`)}
//in effect extending the object
console.log(admin.greet())
//add additional method to the prototype
User.prototype.userAction = function(){console.log(`${this.name} what do you like to learn today`)}
//access the new method (*)
const user2 = new User('Clement')
user2.userAction()
//output
Clement what do you like to learn today
//change the prototype property
User.prototype.greet = function(){console.log(`Hi ${this.name}`)}
//verify the update method
user2.greet() // Hi Clement
Understanding the __proto__
We can access the prototype of an object using the obj.__proto__ syntax.
__proto__ is the internal property of an object, pointing to its prototype.
In the example below, we read the prototype of the object user2
console.log(user2.__proto__)
Reading the prototype with __proto__ is considered outdated and has been deprecated. The current approach is to use the Object.getPrototypeOf(obj)
Object.getPrototypeOf(user2)
We can also set the prototype of an object using the __proto__ .
Take a look at the example below:
const obj = {
greet: function(){console.log('How are you today')}
}
const obj2 = {
}
//set the prototype of obj2
obj2.__proto__ = obj;
//access the greet method
console.log(obj2.greet())
We define an
objwith agreetmethodWe define
obj2with is an empty object literalWe access the
__proto__ofobj2and assign the details ofobj. This will copy all the properties and methods inobjtoobj2Henceforth, we can call the
greetmethod onobj2even though it was not defined thereobj2has inherited thegreetmethod fromobj
Using __proto__ is the same thing as using the extends keyword in OOP languages.
The current approach to setting the prototype of an object is to use the Object.setPrototypeOf(obj, proto) . This sets the [[Prototype]] of the obj to what we define in the proto argument.
The example below changes the prototype of the user2 object
//using user2 object created earlier
Object.setPrototypeOf(user2, {changeProto: function(){console.log('changing the prototype')}}); // change the prototype of user2
We have changed the prototype of
user2fromgreetmethod tochangeProtomethodWe can verify the change by reading the prototype using the
Object.getPrototypeOf(user2)
The output of the above is as below
Importance of prototypes in OOP
A prototype is a flawless approach to defining the OOP model in JavaScript, classes are only syntactic sugar for prototypes.
Whenever we define a class, it is transpiled to a constructor function with an in-built prototype property
Below is an illustration
class User {
greetUser(){
console.log('Hello world')
}
}
//transpiled to
function User {
User.prototype.greetUser = function(){ console.log('Hello world')}
}
Prototypal Inheritance
In programming, inheritance occurs when one object is able to access all the properties and methods in another object.
Unlike other languages like C++ and Java which implements classical inheritance, JavaScript implements Prototypal inheritance.
With classical inheritance, the child class extends the parent class to enable it to inherit the properties and methods defined in the parent class. A definite object can then be built from the instance of the class.
However, we don't need to define class when dealing with prototypal inheritance. We define a object with its properties and methods, access the initial object, and extend it using the prototype. This new object will then inherit all the properties and methods defined in the initial object.
Prototypal inheritance is a mechanism involving objects inheriting from any other objects rather than from classes.
Everything we discussed previously made use of the concept of prototypal inheritance. For instance, we had defined an object user with its properties and methods, then new objects we create will inherit the properties and methods defined in the user object
Why are there so many ways to manage Prototype
There are different approaches to managing [[Prototype]] and most often it becomes confusing. How did that happen and why?
The JavaScript language has had prototypal inheritance included in its since its inception. However, ways to manage it have evolved.
The
prototypeproperty of a constructor function has been the oldest way to create objects with a given prototype.Late 2012,
Object.createprovided the ability to create objects with a given prototype, however, it lacked the ability to get or set the prototype. To give more flexibility to developers, browsers implemented the__proto__accessor allowing the user to get or set a prototype.Object.setPrototypeOfandObject.getPrototypeOfwere added to the standard in 2015, to handle the same functionality as__proto__. As__proto__was the de-factor, it was deprecated and pushed to Annex B of the standard, that is: optional for non-browser environmentsIn 2022. it was officially allowed to use
__proto__in object literals{...}but not as a getter/setter (still in Annex B).
Summary
In Class-based programming, objects are built based on classes.
Prototype-based languages permit the creation of an object without first defining its class.
There is a hidden property in every object called prototype that enables us to inherit properties and methods
The value pointed at by the
[[Prototype]]is casually known as "the prototype of that object"Prototypal inheritance is a mechanism involving objects inheriting from any other objects rather than from classes.
If you have found value in this article, please comment or share it on your social handles. It will be of help to somebody. Follow me on https://twitter.com/emmanuelfkumah for dev tips.










Top comments (16)
Very useful and detailed! Thank you
I found the explanations and examples you provided to be very clear and helpful. It's great to have a better understanding of how the language works. Keep up the good work!
Thanks for this great explanations, for me it's more clear now.
I think it's really important to understand all these concepts, and your article helps me a lot.
Sorry if I am wrong, there is a mistake example just before section «Dimming Properties» with
toUpperCasefunction.I think the explanation is wrong, JS engine don't lookup to
userobject searching fortoUpperCasemethod, because before that,string primitivewas converted intoStringobject ("boxing"), and on that object engine foundtoUpperCasemethod.If
toUpperCasemethod is created onuserobject, we can call it withuser.toUpperCase()and not withuser.name.toUpperCase().Again, thanks, great explanations :)
Thanks for the clarifcation. I will relook at that section
Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍
Yaaaay, thank you all for your valuable feedbacks and reads
Extremely deep post! congrats! Clicked here because I'm a nerd of class vs. prototype and I love this topic. I wrote one about the same topic (but considerably shorter 😅)
great work!
Thanks Manuel, I will take a look at yours too.
Your explanation was clear.
If I may summarize, there are two approaches to Object Oriented Programming(OOP) which are: 1. Classical Approach and 2. Prototype Approach.
The Prototype approach is the default standard for creating OOP in JavaScript.
The Prototype Approach has two methods to achieve OOP viz Using Object.create() method or Using constructor function method.
In all these, using classes for OOP in JavaScript is like a sweetener to writing OOp in JavaScript.
Thanks for your time and clear examples. I have bookmarked this piece and would refer techies to it. Well-done!
Thanks for your feedback. Appreciate it
Quite illustrative, thank you for sharing! 😄
Welcome Joel, please do share on your social handles. It might be of help to others
Thank you
This is awesome bro! Never seen JavaScript this way till reading this article. Thanks for sharing.
Neat