JavaScript - Object Oriented vs Prototype-based programming
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:
- Creates a new object.
- Sets the constructor of the object to the function (
Person
). - Calls the constructor with the context of the newly created object.
- 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.