OOP Under the Hood: Prototypes, new, and More

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:

  1. Creates an empty object

    A new, plain object is created: {}

  2. Sets this to that object

    Inside the constructor function, this now refers to the newly created object.

  3. Links the object to the constructor’s prototype

    The new object’s internal [[Prototype]] (accessible via __proto__) is set to the constructor function’s prototype.

  4. 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 .prototype property.
  • The .prototype object has a .constructor property that points back to the function.
  • When new is 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:

  1. Checks if the property exists on the object itself.
  2. If not, it looks at the object’s prototype (__proto__ or Object.getPrototypeOf(obj)).
  3. This continues recursively up the chain until reaching the end — Object.prototype.
  4. 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 using value.__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
Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
how-to-use-claude-code-safely:-a-non-technical-guide-to-managing-risk

How to Use Claude Code Safely: A Non-Technical Guide to Managing Risk

Related Posts