DEV Community

Cover image for Why JavaScript is a Prototype-based OOP
Emmanuel Fordjour  Kumah
Emmanuel Fordjour Kumah

Posted on

Why JavaScript is a Prototype-based OOP

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__ properties

  • How 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)
Enter fullscreen mode Exit fullscreen mode

In the console, you can view the properties of user, alongside the [[Prototype]] (hidden property).

Javascript proto

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

JS Prototype

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

Javascript prototype

How prototype works

Consider the user object below.

It has three properties: name, age and city.

const user = {
    name: "Emmanuel",
    age: 23,
    city: "Accra"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Prototype chain

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)
Enter fullscreen mode Exit fullscreen mode
  • We define a person object with eats and hasLegs properties, including a walks method

  • We create a man object and set the prototype of man to the person object using man.__proto__= person

  • We create a specific man samuel and set the prototype of samuel to man using samuel.__proto__ = man

  • Now, we try accessing the samuel.walks() and samuel.hasBeard It is clear from the samuel object that there is no walk method and a hasBeard 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 object samuel for the method.

  • If it is not found, the [[Prototype]] of samuel will be searched.

  • The [[Prototype]] has been set to the object man , hence the JavaScript engine checks the man for the walk method.

  • If it is not found, the [[Prototype]] of man will be searched.

  • Its [[Prototype]] has been set to the person , so we check the person for the walk method.

  • Finally, at the top of the prototype chain, we can locate the method walk in the object person and the JavaScript calls the method even though it wasn't defined in the object samuel.

JavaScript prototype

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())
Enter fullscreen mode Exit fullscreen mode

When we call the method toUppercase()

  • The JavaScript engine looks for the toUpperCase in the user object

  • If it can't locate it there, it searches the [[prototype]] property of user for toUpperCase

  • Finds it there, and executes the method.

JS prototype

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()
Enter fullscreen mode Exit fullscreen mode
  • getFullYear() is a built-in method on the myDate object. When executed, it returns the full year

  • We assign a property with the same name to the myDate object, the value of the property is a function that consoles changed the default getFullYear method

  • This approach overwrites the built-in method, this is because the JS engines first check the myDate object for the getFullYear 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
  • We defined a user object, with isLoggedIn property and greetUser method.

  • The Object.create() creates a new admin object.

  • We set the prototype of the admin object to the user using the Object.create(user)

Below is the result of logging the admin to the console

JS Prototype

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 the admin 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"
Enter fullscreen mode Exit fullscreen mode

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 !
Enter fullscreen mode Exit fullscreen mode

The output is as below:

JS Prototype

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 the admin object

  • There is also a hidden [[Prototype]] property, that reference an Object

  • That Object is the existing user we passed to the Object.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 to user.

  • In the user prototype, we have access to the greetUser method

  • Now we can call greetUser() on the admin, and the prototype will handle the implementation.

  • This explains why, the admin.greetUser() executes the method even though it was not defined in the admin .

  • Note that, the this keyword used in the user will reference the newly created object ( in this example admin). Hence, this.name references Emmanuel

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
Enter fullscreen mode Exit fullscreen mode
  • We define a constructor function User, with name and age properties

  • The User has a prototype property that allows us to assign the greet method.

  • We created an admin object from the User

  • Now, even though greet has not been defined in the admin, it inherits that method from the prototype.

  • This explains why we can call the greet method on the admin 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
Enter fullscreen mode Exit fullscreen mode

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__)
Enter fullscreen mode Exit fullscreen mode

JS Prototype

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)
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode
  • We define an obj with a greet method

  • We define obj2 with is an empty object literal

  • We access the __proto__ of obj2 and assign the details of obj . This will copy all the properties and methods in obj to obj2

  • Henceforth, we can call the greet method on obj2 even though it was not defined there

  • obj2 has inherited the greet method from obj

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
Enter fullscreen mode Exit fullscreen mode
  • We have changed the prototype of user2 from greet method to changeProto method

  • We can verify the change by reading the prototype using the Object.getPrototypeOf(user2)

The output of the above is as below

Javascript prototype

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')}
}
Enter fullscreen mode Exit fullscreen mode

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 and Object.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 environments

  • In 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.

Oldest comments (15)

Collapse
 
manuartero profile image
Manuel Artero Anguita 🟨

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!

Collapse
 
efkumah profile image
Emmanuel Fordjour Kumah • Edited

Thanks Manuel, I will take a look at yours too.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇

Quite illustrative, thank you for sharing! 😄

Collapse
 
efkumah profile image
Emmanuel Fordjour Kumah

Welcome Joel, please do share on your social handles. It might be of help to others

Collapse
 
sharmi2020 profile image
Sharmila kannan

Very useful and detailed! Thank you

Collapse
 
bybydev profile image
byby

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!

Collapse
 
fruntend profile image
fruntend

Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍

Collapse
 
efkumah profile image
Emmanuel Fordjour Kumah

Yaaaay, thank you all for your valuable feedbacks and reads

Collapse
 
prodbyola profile image
prodbyola

This is awesome bro! Never seen JavaScript this way till reading this article. Thanks for sharing.

Collapse
 
tonnerkiller profile image
tonnerkiller

Great article that will help me understand OOP in JS better.
One thing still seems strange: If I understand correctly, there are two syntactical approaches in JS that cannot be mixed, one being the proto approach of the objects and the other being the prototype property of functions.
So my objects would be either classes or functions, but I should not mix. Right?

Collapse
 
vinaybedre profile image
Vinay

Neat

Collapse
 
goatscrub profile image
goatscrub

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 toUpperCase function.

I think the explanation is wrong, JS engine don't lookup to user object searching for toUpperCase method, because before that, string primitive was converted into String object ("boxing"), and on that object engine found toUpperCase method.

If toUpperCase method is created on user object, we can call it with user.toUpperCase() and not with user.name.toUpperCase().

Again, thanks, great explanations :)

Collapse
 
efkumah profile image
Emmanuel Fordjour Kumah

Thanks for the clarifcation. I will relook at that section

Collapse
 
iwarimie profile image
aduonye

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!

Collapse
 
efkumah profile image
Emmanuel Fordjour Kumah

Thanks for your feedback. Appreciate it