DEV Community

Vishal Kinikar
Vishal Kinikar

Posted on

Deep Dive into Prototypes: The Backbone of JavaScript

Prototypes are a core concept in JavaScript, forming the foundation of its object-oriented programming (OOP) capabilities. While other languages use classes as the basis for inheritance, JavaScript relies on prototypes. In this article, we’ll explore prototypes in depth and uncover how they power inheritance, object behavior, and more in JavaScript.


What Are Prototypes?

In JavaScript, every object has an internal property called [[Prototype]] that points to another object. This is the prototype of the object, and it acts as a fallback mechanism for properties or methods that are not found directly on the object.

Prototype Chain

The prototype chain is a series of linked prototypes. If a property or method isn't found on an object, JavaScript looks up the chain until it reaches null.

const parent = { greet: () => console.log("Hello from parent!") };
const child = Object.create(parent);

child.greet();  // Output: "Hello from parent!"
console.log(child.hasOwnProperty('greet'));  // Output: false
Enter fullscreen mode Exit fullscreen mode

Here, child doesn't have a greet method, so JavaScript looks up the prototype chain to parent and finds it there.


The __proto__ and prototype Confusion

JavaScript provides two different terms related to prototypes that can be confusing:

  1. __proto__:

    • This is an accessor property available on all objects that points to the object's prototype.
    • It's a way to access the [[Prototype]] of an object.
  2. prototype:

    • This is a property available only on functions (specifically constructor functions).
    • It’s used to define the prototype of objects created by that function.

Example:

function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function () {
  console.log(`Hello, my name is ${this.name}`);
};

const alice = new Person("Alice");

console.log(alice.__proto__ === Person.prototype);  // true
alice.sayHello();  // Output: "Hello, my name is Alice"
Enter fullscreen mode Exit fullscreen mode

Prototype Inheritance in Action

JavaScript’s inheritance is prototype-based, meaning objects inherit directly from other objects rather than classes.

Creating Inheritance

const animal = {
  eat() {
    console.log("Eating...");
  }
};

const dog = Object.create(animal);
dog.bark = function () {
  console.log("Barking...");
};

dog.eat();  // Output: "Eating..."
dog.bark(); // Output: "Barking..."
Enter fullscreen mode Exit fullscreen mode

The dog object inherits the eat method from the animal object.


Using Object.create for Clean Inheritance

The Object.create method creates a new object with a specified prototype. It's a cleaner and more intuitive way to set up inheritance.

Example:

const person = {
  introduce() {
    console.log(`Hi, I'm ${this.name}`);
  }
};

const student = Object.create(person);
student.name = "John";
student.introduce();  // Output: "Hi, I'm John"
Enter fullscreen mode Exit fullscreen mode

Extending Built-In Prototypes

While extending built-in prototypes like Array or Object is possible, it’s generally discouraged as it can lead to conflicts.

Example:

Array.prototype.last = function () {
  return this[this.length - 1];
};

console.log([1, 2, 3].last());  // Output: 3
Enter fullscreen mode Exit fullscreen mode

Why Avoid It?

  • Compatibility issues: Other libraries might rely on default prototypes.
  • Maintenance: Your changes might break existing code.

Prototypes vs Classes

With ES6, JavaScript introduced class syntax, providing a more familiar OOP experience. However, under the hood, classes still use prototypes.

Example:

class Animal {
  eat() {
    console.log("Eating...");
  }
}

class Dog extends Animal {
  bark() {
    console.log("Barking...");
  }
}

const dog = new Dog();
dog.eat();  // Output: "Eating..."
dog.bark(); // Output: "Barking..."
Enter fullscreen mode Exit fullscreen mode

Even with class, the inheritance is prototype-based.


The Performance Angle

Prototype-based inheritance is more memory-efficient because methods are shared across instances rather than being duplicated.

Example:

function Car(make) {
  this.make = make;
}

Car.prototype.drive = function () {
  console.log(`${this.make} is driving`);
};

const car1 = new Car("Toyota");
const car2 = new Car("Honda");

console.log(car1.drive === car2.drive);  // true
Enter fullscreen mode Exit fullscreen mode

Here, drive is not duplicated for each car; instead, both instances share the same method.


Key Takeaways

  1. Prototypes Enable Inheritance: Objects inherit from other objects through their prototype.
  2. Prototype Chain: JavaScript resolves properties and methods by traversing the prototype chain.
  3. Object.create: A clean way to set up inheritance.
  4. Avoid Extending Built-In Prototypes: It can lead to unexpected behavior and conflicts.
  5. Classes Use Prototypes: ES6 classes provide syntactic sugar but still rely on prototypes under the hood.

Understanding prototypes is essential for mastering JavaScript. While ES6 classes have made object-oriented programming in JavaScript more approachable, the prototype system remains at the core of the language. By diving deep into prototypes, you unlock the ability to write efficient, scalable, and maintainable code.

Top comments (0)