Web & Mobile Development / Web Development / Mobile Development / Other / JavaScript

JavaScript - Object Oriented vs Prototype-based programming

March 29, 20255 min read

JavaScript - Object-Oriented vs. Prototype-Based Programming

Object-Oriented vs. Prototype-Based Programming

I believe you’ve already heard about classes and know some basics, like how new objects are created during instantiation from classes. In a “class-based” language, copying from a class happens at compile time. However, classical inheritance is much more complicated and involves terms that don’t exist in JavaScript:

  • Classes
  • Objects
  • Interfaces
  • Abstract classes
  • Final classes
  • Virtual base classes
  • Constructors
  • Destructors

These were defined to achieve concepts such as:

  • Encapsulation
  • Abstraction
  • Inheritance
  • Polymorphism

As I mentioned, in a “class-based” language, copying from a class happens at compile time. However, in prototypal inheritance, there is no copying; instead, we have a reference to the prototype and its methods.

What is a Prototype?

In a prototypal system, objects inherit from other objects through a mechanism that looks for methods in __proto__. The __proto__ property is created on an object when instantiated using the new keyword. When you define a new function or class, it has a prototype property instead. Copy the code below into the console and compare:

function Person() {}
console.log(Person.prototype);
console.log(new Person().__proto__);

For example, let’s say we have a Woman class that inherits from a Human class in JavaScript. When you instantiate a Woman object, its __proto__ will point to Woman, and Woman's __proto__ will point to Human. All class members defined in Human but not in Woman will be accessible in an instance of the Woman class. When a method is invoked, the object first looks in its own members; if it is not found, it checks __proto__, and if not found there, it continues up the prototype chain. Try this example in your console:

class Human {
    sayHi() {
        console.log('Hi');
    }
}

class Woman extends Human {}

const woman = new Woman();

console.log(woman.__proto__);
console.log(woman.__proto__.__proto__);

console.log(woman.__proto__ === Woman.prototype); // true
console.log(woman.__proto__.__proto__ === Human.prototype); // true

“Instantiation” in JavaScript

The first way to instantiate an object is by using a constructor function. It’s a function like any other, but we can define its prototype property, which will be used as the __proto__ property for newly created objects when using the new keyword.

Let's slow down and first look at how we declare a constructor function:

function Person() {}

Yes, you’re right—it’s just a function. A good practice is to start a constructor function name with an uppercase letter to distinguish it from regular functions, but this is not required.

By default, the prototype of this function is an object that inherits from Object, so its __proto__ property is set to Object. Additionally, it has a constructor property:

Person.prototype;
// { constructor: f, __proto__: Object }

But what is the constructor? It’s a property that points to our function, which in this case is Person:

{
  constructor: Person,
  __proto__: Object
}

The purpose of constructor is to indicate which function was used to create an object.

Constructor Function

Let’s create a simple constructor function and instantiate an object. Try running the following code in the console:

function Person(name) {
  this.name = name;
  console.log(this);
  console.log(this.__proto__);
}
Person.prototype.sayHello = function () { console.log(this.name); };
const matt = new Person('Matt');
matt.sayHello();
// 'Matt'

console.log(matt.__proto__ === Person.prototype); // true

prototype is an object with a constructor property that references the constructor function (Person). Every function has a prototype with this property, which points to that function:

function getHello() { return 'Hello World'; }
getHello.prototype.constructor === getHello;
// true

The prototype is not available on instances themselves (or other objects), but only on constructor functions.

Object.create

Object.create creates a new object with its prototype set to the passed-in object, i.e.:

const obj = {};
const obj2 = Object.create(obj);
obj2.__proto__ === obj;
// true

The Object.create function simplifies JavaScript’s constructor pattern, achieving true prototypal inheritance. Instead of creating classes, you create prototype objects and then use Object.create to make new instances.

Class in JavaScript vs. Constructor Function

class Person {
  constructor(name) {
    this.name = name;
    console.log(this);
  }
  sayHello() {
    console.log(this.name);
  }
}

The above is equivalent to using a constructor function:

function Person(name) {
  this.name = name;
  console.log(this);
}
Person.prototype.sayHello = function() {
  console.log(this.name);
};
console.log(new Person('Matt'));

Essentially, the new operator in JavaScript:

  1. Creates a new object.
  2. Sets the constructor of the object to the function (Person).
  3. Calls the constructor with the context of the newly created object.
  4. Returns this, unless the constructor explicitly returns a different object.

Inheritance Comparison

Using ES6 class syntax:

class Human {
  constructor(name) {
    this.name = name;
  }
  sayName() { console.log(this.name); }
}

class Woman extends Human {
  gender = 'female';
}

const eva = new Woman('Eva');

If you don’t specify a constructor method, a default empty constructor is used:

constructor(...args) {
  super(...args);
}

In function-based syntax, it would look like this:

function HumanFn(name) {
  this.name = name;
}
HumanFn.prototype.sayName = function() { console.log(this.name); };

function WomanFn(name) {
  HumanFn.call(this, name);
  this.gender = 'female';
}
WomanFn.prototype = Object.create(HumanFn.prototype);

const evaFn = new WomanFn('Eva');

Instead of using extends, you must manually set WomanFn's prototype.

Summary

In class-based languages, objects are created by copying from classes at compile time, involving concepts such as encapsulation, abstraction, inheritance, and polymorphism. On the other hand, prototype-based programming in JavaScript allows objects to inherit directly from other objects through the __proto__ mechanism without requiring class definitions.

I hope you take the time to experiment with the provided code snippets to better understand JavaScript’s prototype mechanism.

Web DevelopmentMobile DevelopmentJavaScript