Object-Oriented Programming (OOP) in JavaScript works differently from traditional class-based languages like Java or C++. Under the hood, everything is powered by prototypes, objects that other objects can inherit properties and methods from.
This guide explores how JavaScript implements OOP features such as new, prototypes, and inheritance.
The new Keyword
When you use the new keyword in JavaScript, several things happen behind the scenes.
Using new with a constructor function is what allows you to create instances that share behavior through a prototype.
Here’s what the new keyword does step by step:
-
Creates an empty object
A new, plain object is created:
{} -
Sets
thisto that objectInside the constructor function,
thisnow refers to the newly created object. -
Links the object to the constructor’s prototype
The new object’s internal
[[Prototype]](accessible via__proto__) is set to the constructor function’sprototype. -
Returns the object automatically
Unless the constructor explicitly returns another object, the new object (
this) is returned by default.
Example: Using new with Constructor Functions
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hi, my name is ${this.name} and I am ${this.age} years old.`);
};
const person1 = new Person('Alice', 25);
person1.greet(); // Hi, my name is Alice and I am 25 years old.
Under the hood, when new Person('Alice', 25) runs, JavaScript does the following:
const obj = {}; // Step 1
Object.setPrototypeOf(obj, Person.prototype); // Step 3
Person.call(obj, 'Alice', 25); // Step 2
return obj; // Step 4
Prototypes
In JavaScript, every function automatically gets a property called prototype.
This object is where you define methods and properties that should be shared among all instances created by that function using new.
When you define methods inside the prototype, they are not duplicated across instances — instead, all instances share the same reference to that method.
This is memory-efficient and allows inheritance behavior.
Example: Methods in the Prototype
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.start = function() {
console.log(`${this.make} ${this.model} is starting...`);
};
const car1 = new Car('Toyota', 'Corolla');
const car2 = new Car('Honda', 'Civic');
console.log(car1.start === car2.start); // true (shared prototype method)
Key Facts
- Every function has a
.prototypeproperty. - The
.prototypeobject has a.constructorproperty that points back to the function. - When
newis used, a link is established between the created object and the constructor’s.prototype.
The Prototype Chain
The prototype chain is how JavaScript handles property lookup and inheritance.
When you access a property or method on an object, JavaScript:
- Checks if the property exists on the object itself.
- If not, it looks at the object’s prototype (
__proto__orObject.getPrototypeOf(obj)). - This continues recursively up the chain until reaching the end —
Object.prototype. - If the property isn’t found anywhere in the chain, JavaScript returns
undefined.
Example: How the Prototype Chain Works
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound.`);
};
const dog = new Animal('Buddy');
dog.speak(); // Buddy makes a sound.
// Property lookup chain:
console.log(dog.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true (end of the chain)
Classes, Inheritance, and Prototypes
The ES6 class syntax is just syntactic sugar over JavaScript’s prototype system.
Everything that happens with class and extends is built upon prototypes internally.
Example: Inheritance with ES6 Classes
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const rex = new Dog('Rex');
rex.speak(); // Rex barks.
Behind the scenes, this is equivalent to linking prototypes manually:
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound.`);
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
console.log(`${this.name} barks.`);
};
__proto__ vs prototype
| Term | Belongs To | Description |
|---|---|---|
prototype |
Functions | The object used as a template for new instances created by that function. |
__proto__ |
Instances (objects) | A reference to the object’s internal [[Prototype]], usually the constructor’s .prototype. |
Example
function User(name) {
this.name = name;
}
const u = new User('Karen');
console.log(User.prototype); // The prototype object
console.log(u.__proto__); // Points to User.prototype
console.log(u.__proto__ === User.prototype); // true
Useful Prototype Methods
-
Object.create(prototype)
Creates a new object with its internal prototype set to the specified one.
const animal = { eats: true };
const dog = Object.create(animal);
console.log(dog.eats); // true
-
Object.getPrototypeOf(value)
Returns the prototype of a given object. Equivalent to usingvalue.__proto__.
console.log(Object.getPrototypeOf(dog) === animal); // true
-
Object.setPrototypeOf(object, newPrototype)
Changes the prototype of an existing object (use with caution for performance reasons).
Object.setPrototypeOf(dog, null);
console.log(Object.getPrototypeOf(dog)); // null