The Secret Life of JavaScript: Understanding Prototypes

Published: (December 10, 2025 at 11:07 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Objects Delegate, They Don’t Copy

Timothy arrived at the library with his laptop and a puzzled expression. He’d been reading about JavaScript inheritance, and something didn’t add up.

Timothy: “I learned that JavaScript uses prototypes for inheritance. But I’ve also learned that JavaScript has classes now. So which is it? Does JavaScript use prototypes or classes?”
Margaret: “Both. And neither. It’s more subtle than that.”

What is inheritance?

Timothy: “When a child class inherits from a parent class, it gets all the parent’s properties and methods.”
Margaret: “In languages like Python and Java, yes. The child class copies or includes the parent’s behavior. JavaScript, however, uses delegation.”

Delegation vs. copying

Instead of copying properties down, objects in JavaScript point to other objects and say: “If I don’t have what you’re looking for, ask that object over there.”

Simple example

const parent = {
  greet: function() {
    console.log("Hello, " + this.name);
  }
};

const child = Object.create(parent);
child.name = "Alice";

child.greet(); // "Hello, Alice"
  • Object.create(parent) creates a new object whose hidden [[Prototype]] link points to parent.
  • When child.greet() is called, JavaScript looks for greet on child. Not found → looks on child’s prototype (parent). Finds it and calls it with this bound to child.

Multiple objects sharing the same prototype

const parent = {
  greet: function() {
    console.log("Hello, " + this.name);
  }
};

const alice = Object.create(parent);
alice.name = "Alice";

const bob = Object.create(parent);
bob.name = "Bob";

alice.greet(); // "Hello, Alice"
bob.greet();   // "Hello, Bob"

Both alice and bob delegate to the same parent object, but this inside greet refers to the caller.

[[Prototype]] vs. .prototype

const obj = Object.create(parent);
// obj.[[Prototype]] points to parent (invisible)

The .prototype property on functions

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

// User.prototype is a regular property on the User function
console.log(User.prototype); // { constructor: User }

Using new

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

User.prototype.greet = function() {
  console.log("Hello, " + this.name);
};

const alice = new User("Alice");
alice.greet(); // "Hello, Alice"

When new User("Alice") runs, JavaScript:

  1. Creates a brand‑new object.
  2. Sets that object’s [[Prototype]] to User.prototype.
  3. Calls User with this bound to the new object.
  4. Returns the new object.

You can verify the link:

console.log(Object.getPrototypeOf(alice) === User.prototype); // true

Object.getPrototypeOf() (or the legacy __proto__) reveals the invisible prototype chain.

Classes: Syntactic Sugar Over Prototypes

Class version of the previous example

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log("Hello, " + this.name);
  }
}

const alice = new User("Alice");
alice.greet(); // "Hello, Alice"
  • The class syntax creates the same prototype chain under the hood.
  • Methods defined inside a class (greet) are placed on User.prototype.
  • new User() still creates an object whose [[Prototype]] points to User.prototype.

Why “syntactic sugar”?

The term means the syntax looks different and is often clearer, but the underlying mechanism—prototype delegation—remains unchanged. Classes also provide convenient keywords like extends and super(), which handle prototype chaining for you, but they do not introduce a new inheritance model.

Summary

  • JavaScript objects delegate to other objects via the hidden [[Prototype]] link.
  • The .prototype property on constructor functions is a way to set up that hidden link for objects created with new.
  • Object.getPrototypeOf() (or __proto__) lets you inspect the prototype chain.
  • ES6 classes are merely a cleaner way to write the same prototype‑based pattern; they do not replace prototypes.
Back to Blog

Related posts

Read more »