Why JavaScript is a Prototype-based OOP
Emmanuel Fordjour Kumah
Posted on March 30, 2023
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
person
object witheats
andhasLegs
properties, including awalks
methodWe create a
man
object and set the prototype of man to theperson
object usingman.__proto__= person
We create a specific man
samuel
and set the prototype ofsamuel
toman
usingsamuel.__proto__ = man
Now, we try accessing the
samuel.walks()
andsamuel.hasBeard
It is clear from thesamuel
object that there is nowalk
method and ahasBeard
property.
How then, do we get the output I can walk
and true
?
Whenever
samuel.walks()
is called, the JavaScript engine will first search the objectsamuel
for the method.If it is not found, the
[[Prototype]]
ofsamuel
will be searched.The
[[Prototype]]
has been set to the objectman
, hence the JavaScript engine checks theman
for thewalk
method.If it is not found, the
[[Prototype]]
ofman
will be searched.Its
[[Prototype]]
has been set to theperson
, so we check theperson
for thewalk
method.Finally, at the top of the prototype chain, we can locate the method
walk
in the objectperson
and 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
toUpperCase
in theuser
objectIf it can't locate it there, it searches the
[[prototype]]
property ofuser
fortoUpperCase
Finds 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 themyDate
object. When executed, it returns the full yearWe assign a property with the same name to the
myDate
object, the value of the property is a function that consoleschanged the default getFullYear method
This approach overwrites the built-in method, this is because the JS engines first check the
myDate
object for thegetFullYear
property. 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
user
object, withisLoggedIn
property andgreetUser
method.The
Object.create()
creates a newadmin
object.We set the prototype of the
admin
object to theuser
using 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
admin
object, it looks empty. However, we notice there exists a[[Prototype]]
property. This property is in-built and references the prototype of theadmin
object.We conclude that the prototype has been set to the existing
user
object.
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
name
is the only visible property in theadmin
objectThere is also a hidden
[[Prototype]]
property, that reference anObject
That
Object
is the existinguser
we 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
admin
has been set touser
.In the
user
prototype, we have access to thegreetUser
methodNow 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
this
keyword used in theuser
will reference the newly created object ( in this exampleadmin
). Hence,this.name
referencesEmmanuel
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
, withname
andage
propertiesThe
User
has aprototype
property that allows us to assign thegreet
method.We created an
admin
object from theUser
Now, even though
greet
has not been defined in theadmin
, it inherits that method from the prototype.This explains why we can call the
greet
method on theadmin
object.
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
obj
with agreet
methodWe define
obj2
with is an empty object literalWe access the
__proto__
ofobj2
and assign the details ofobj
. This will copy all the properties and methods inobj
toobj2
Henceforth, we can call the
greet
method onobj2
even though it was not defined thereobj2
has inherited thegreet
method 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
user2
fromgreet
method tochangeProto
methodWe 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
prototype
property of a constructor function has been the oldest way to create objects with a given prototype.Late 2012,
Object.create
provided 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.setPrototypeOf
andObject.getPrototypeOf
were 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.
Posted on March 30, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.