Docs

README

7.5 Prototypes

Table of Contents

  1. β€’Introduction
  2. β€’What is a Prototype?
  3. β€’The Prototype Chain
  4. β€’Accessing Prototypes
  5. β€’Setting Prototypes
  6. β€’Prototype Inheritance
  7. β€’Constructor Functions and Prototypes
  8. β€’The prototype Property
  9. β€’Shadowing Properties
  10. β€’Best Practices

Introduction

Prototypes are JavaScript's mechanism for inheritance. Every object in JavaScript has a hidden internal property called [[Prototype]] that references another object.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  JavaScript Prototype System                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚   Every object has a [[Prototype]]                          β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                           β”‚
β”‚   β”‚   myObj     β”‚                                           β”‚
β”‚   β”‚  ─────────  β”‚                                           β”‚
β”‚   β”‚  name: "X"  │──────► [[Prototype]]                      β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚                            β”‚
β”‚                                β–Ό                            β”‚
β”‚                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                      β”‚
β”‚                        β”‚Object.proto β”‚                      β”‚
β”‚                        β”‚  ─────────  β”‚                      β”‚
β”‚                        β”‚ toString()  β”‚                      β”‚
β”‚                        β”‚ valueOf()   β”‚                      β”‚
β”‚                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                      β”‚
β”‚                                β”‚                            β”‚
β”‚                                β–Ό                            β”‚
β”‚                              null                           β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What is a Prototype?

A prototype is an object from which other objects inherit properties. When you try to access a property on an object:

  1. β€’JavaScript first looks for the property on the object itself
  2. β€’If not found, it looks on the object's prototype
  3. β€’This continues up the prototype chain until found or reaching null
const animal = {
  eats: true,
  walk() {
    console.log('Walking...');
  },
};

const rabbit = {
  jumps: true,
};

// Set animal as rabbit's prototype
Object.setPrototypeOf(rabbit, animal);

console.log(rabbit.jumps); // true (own property)
console.log(rabbit.eats); // true (inherited from animal)
rabbit.walk(); // "Walking..." (inherited method)

The Prototype Chain

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Prototype Chain Example                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                     β”‚
β”‚   rabbit                  animal               Object.prototype     β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚   β”‚ jumps:   β”‚  proto    β”‚ eats:    β”‚  proto  β”‚ toString()   β”‚      β”‚
β”‚   β”‚   true   │─────────► β”‚   true   │───────► β”‚ valueOf()    β”‚      β”‚
β”‚   β”‚          β”‚           β”‚ walk()   β”‚         β”‚ hasOwnProp() β”‚      β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                                                      β”‚              β”‚
β”‚                                                      β–Ό              β”‚
β”‚                                                    null             β”‚
β”‚                                                                     β”‚
β”‚   Property Lookup for rabbit.toString():                            β”‚
β”‚   1. Not in rabbit ──► 2. Not in animal ──► 3. Found in Object.protoβ”‚
β”‚                                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
const rabbit = { jumps: true };
const animal = { eats: true };

Object.setPrototypeOf(rabbit, animal);

// Prototype chain lookup
console.log(rabbit.hasOwnProperty('jumps')); // true
console.log(rabbit.hasOwnProperty('eats')); // false (inherited)

// Check the chain
console.log(Object.getPrototypeOf(rabbit) === animal); // true
console.log(Object.getPrototypeOf(animal) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null

Accessing Prototypes

Object.getPrototypeOf()

The recommended way to get an object's prototype:

const arr = [1, 2, 3];
const proto = Object.getPrototypeOf(arr);

console.log(proto === Array.prototype); // true

proto (Deprecated)

The legacy way - avoid in new code:

const obj = {};
console.log(obj.__proto__ === Object.prototype); // true

// Works but deprecated
obj.__proto__ = { custom: true };

isPrototypeOf()

Check if an object is in another's prototype chain:

const animal = { eats: true };
const rabbit = Object.create(animal);

console.log(animal.isPrototypeOf(rabbit)); // true
console.log(Object.prototype.isPrototypeOf(rabbit)); // true

Setting Prototypes

Object.create()

Create object with specific prototype (recommended):

const personProto = {
  greet() {
    return `Hello, I'm ${this.name}`;
  },
  get info() {
    return `${this.name}, ${this.age} years old`;
  },
};

const john = Object.create(personProto);
john.name = 'John';
john.age = 30;

console.log(john.greet()); // "Hello, I'm John"
console.log(john.info); // "John, 30 years old"

Object.setPrototypeOf()

Change an existing object's prototype (use sparingly):

const dog = { barks: true };
const animal = { eats: true };

Object.setPrototypeOf(dog, animal);
console.log(dog.eats); // true

// ⚠️ Warning: This is slow - avoid if possible

Object.create(null)

Create object with no prototype:

const pureDict = Object.create(null);
pureDict.key = 'value';

console.log(pureDict.toString); // undefined (no Object.prototype)
console.log('key' in pureDict); // true

// Useful for dictionaries to avoid prototype pollution

Prototype Inheritance

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Prototype Inheritance Pattern                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  // Base prototype                                          β”‚
β”‚  const vehicle = {                                          β”‚
β”‚    start() { console.log("Starting..."); },                 β”‚
β”‚    stop() { console.log("Stopping..."); }                   β”‚
β”‚  };                                                         β”‚
β”‚                                                             β”‚
β”‚  // Child prototype inherits from vehicle                   β”‚
β”‚  const car = Object.create(vehicle);                        β”‚
β”‚  car.honk = function() { console.log("Beep!"); };           β”‚
β”‚                                                             β”‚
β”‚  // Instance inherits from car (and vehicle)                β”‚
β”‚  const myCar = Object.create(car);                          β”‚
β”‚  myCar.make = "Toyota";                                     β”‚
β”‚                                                             β”‚
β”‚  myCar.start();  // "Starting..." (from vehicle)            β”‚
β”‚  myCar.honk();   // "Beep!" (from car)                      β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example: Multi-Level Inheritance

// Level 1: Base
const living = {
  breathe() {
    console.log(`${this.name} is breathing`);
  },
};

// Level 2: Inherits from living
const animal = Object.create(living);
animal.eat = function () {
  console.log(`${this.name} is eating`);
};

// Level 3: Inherits from animal
const dog = Object.create(animal);
dog.bark = function () {
  console.log(`${this.name} says woof!`);
};

// Instance
const rex = Object.create(dog);
rex.name = 'Rex';

rex.breathe(); // "Rex is breathing"
rex.eat(); // "Rex is eating"
rex.bark(); // "Rex says woof!"

Constructor Functions and Prototypes

When you create objects with new, the constructor's prototype property becomes the new object's [[Prototype]].

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Constructor Function and prototype Property                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                     β”‚
β”‚   function Person(name) {          Person.prototype                 β”‚
β”‚     this.name = name;              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚   }                                β”‚ constructor:    β”‚              β”‚
β”‚                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚   Person        β”‚              β”‚
β”‚   Person.prototype β”€β”€β”€β”€β”˜           β”‚ greet: fn()     β”‚              β”‚
β”‚                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚                                           β–²                         β”‚
β”‚   const john = new Person("John")         β”‚ [[Prototype]]           β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                        β”‚                         β”‚
β”‚   β”‚ name: "John" β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                         β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                  β”‚
β”‚                                                                     β”‚
β”‚   john.greet() β†’ looks in john β†’ not found β†’ looks in prototype     β”‚
β”‚                                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// Add methods to prototype (shared by all instances)
Person.prototype.greet = function () {
  return `Hi, I'm ${this.name}`;
};

Person.prototype.birthday = function () {
  this.age++;
  return `Happy birthday! Now ${this.age}`;
};

const alice = new Person('Alice', 30);
const bob = new Person('Bob', 25);

console.log(alice.greet()); // "Hi, I'm Alice"
console.log(bob.greet()); // "Hi, I'm Bob"

// Both share the same prototype methods
console.log(alice.greet === bob.greet); // true
console.log(Object.getPrototypeOf(alice) === Person.prototype); // true

The prototype Property

Only functions have a prototype property. This is NOT the function's own prototype - it's the prototype that will be assigned to objects created with new.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              prototype vs [[Prototype]]                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                     β”‚
β”‚  Function.prototype     ← Function's OWN prototype (for inheritance)β”‚
β”‚                                                                     β”‚
β”‚  MyFunction.prototype   ← Template for objects created with `new`   β”‚
β”‚                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚                                                             β”‚    β”‚
β”‚  β”‚  function Dog(name) {                                       β”‚    β”‚
β”‚  β”‚    this.name = name;                                        β”‚    β”‚
β”‚  β”‚  }                                                          β”‚    β”‚
β”‚  β”‚                                                             β”‚    β”‚
β”‚  β”‚  Dog.prototype.bark = function() { ... }                    β”‚    β”‚
β”‚  β”‚  ↑                                                          β”‚    β”‚
β”‚  β”‚  This is what new Dog() instances will inherit from         β”‚    β”‚
β”‚  β”‚                                                             β”‚    β”‚
β”‚  β”‚  Object.getPrototypeOf(Dog) === Function.prototype          β”‚    β”‚
β”‚  β”‚  ↑                                                          β”‚    β”‚
β”‚  β”‚  This is Dog's own prototype (it inherits from Function)    β”‚    β”‚
β”‚  β”‚                                                             β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
function Dog(name) {
  this.name = name;
}

// Dog.prototype - template for instances
Dog.prototype.bark = function () {
  console.log('Woof!');
};

// Dog's own prototype - it's a function
console.log(Object.getPrototypeOf(Dog) === Function.prototype); // true

const rex = new Dog('Rex');

// rex's prototype is Dog.prototype
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // true

// The constructor property
console.log(Dog.prototype.constructor === Dog); // true
console.log(rex.constructor === Dog); // true

Shadowing Properties

When an object has a property with the same name as one in its prototype, the object's property "shadows" the prototype's.

const parent = {
  value: 10,
  getValue() {
    return this.value;
  },
};

const child = Object.create(parent);
child.value = 20; // Shadows parent.value

console.log(parent.getValue()); // 10
console.log(child.getValue()); // 20 (uses child.value)

// Check where properties come from
console.log(child.hasOwnProperty('value')); // true
console.log(child.hasOwnProperty('getValue')); // false

// Delete shadows to reveal inherited
delete child.value;
console.log(child.getValue()); // 10 (now uses parent.value)

Shadowing with Getters/Setters

const parent = {
  _value: 10,
  get value() {
    return this._value;
  },
  set value(v) {
    this._value = v;
  },
};

const child = Object.create(parent);

// This uses parent's setter, setting _value on child
child.value = 20;

console.log(child._value); // 20 (own property)
console.log(parent._value); // 10 (unchanged)
console.log(child.value); // 20

Common Prototype Patterns

Extending Built-in Prototypes (Use Carefully!)

// Adding to Array.prototype (generally discouraged)
if (!Array.prototype.last) {
  Object.defineProperty(Array.prototype, 'last', {
    get() {
      return this[this.length - 1];
    },
  });
}

const arr = [1, 2, 3];
console.log(arr.last); // 3

Prototype-Based Mixin

const canWalk = {
  walk() {
    console.log(`${this.name} is walking`);
  },
};

const canSwim = {
  swim() {
    console.log(`${this.name} is swimming`);
  },
};

const canFly = {
  fly() {
    console.log(`${this.name} is flying`);
  },
};

// Create a duck with multiple capabilities
function Duck(name) {
  this.name = name;
}

Object.assign(Duck.prototype, canWalk, canSwim, canFly);

const donald = new Duck('Donald');
donald.walk(); // "Donald is walking"
donald.swim(); // "Donald is swimming"
donald.fly(); // "Donald is flying"

Best Practices

1. Use Object.create() for Prototypal Inheritance

// βœ“ Good
const child = Object.create(parent);

// βœ— Avoid
child.__proto__ = parent;

2. Don't Modify Object.prototype

// βœ— Very dangerous - affects ALL objects
Object.prototype.myMethod = function () {};

// βœ“ Create your own base object
const myBase = {
  myMethod() {},
};

3. Use Object.hasOwn() to Check Own Properties

// βœ“ Good (ES2022+)
if (Object.hasOwn(obj, 'prop')) {
}

// βœ“ Also good (older approach)
if (Object.prototype.hasOwnProperty.call(obj, 'prop')) {
}

// βœ— Can be overridden
if (obj.hasOwnProperty('prop')) {
}

4. Prefer Composition Over Inheritance

// βœ“ Good - Composition
const canFly = (obj) => ({
  fly() {
    console.log(`${obj.name} flies`);
  },
});

const bird = { name: 'Eagle' };
Object.assign(bird, canFly(bird));

// Rather than deep inheritance chains

5. Use Classes for Constructor-Based Patterns

// βœ“ Modern - clearer syntax
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {}
}

class Dog extends Animal {
  bark() {}
}

Summary Table

ConceptDescription
[[Prototype]]Internal link to prototype object
Object.getPrototypeOf()Get an object's prototype
Object.setPrototypeOf()Set an object's prototype
Object.create()Create object with specific prototype
Func.prototypeTemplate for objects created with new Func()
instanceofCheck if object inherits from constructor
isPrototypeOf()Check if object is in prototype chain

Files in This Section

  • β€’README.md - This documentation
  • β€’examples.js - Runnable code examples
  • β€’exercises.js - Practice exercises with solutions

Navigation

README - JavaScript Tutorial | DeepML