JavaScript Prototypes

Every JavaScript object has a prototype. Understanding the prototype chain explains how property lookup works, how inheritance is implemented, and why classes are just syntactic sugar.

Intermediate 11 min read 10 examples

What is a Prototype?

Every JavaScript object has a hidden internal link ([[Prototype]]) pointing to another object - its prototype. When you access a property that does not exist on the object, JavaScript looks for it on the prototype, then the prototype's prototype, and so on.

JavaScript
const arr = [1, 2, 3];

// arr has its own property: the elements
console.log(arr[0]); // 1

// But arr.push is NOT on arr directly - it is on Array.prototype
console.log(Object.getOwnPropertyNames(arr)); // ['0','1','2','length']
console.log(typeof arr.push);  // "function" (found on prototype)

// The chain: arr -> Array.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null (end of chain)

// Same for plain objects
const obj = { name: "Alice" };
// obj -> Object.prototype -> null
console.log(typeof obj.hasOwnProperty); // "function" (from Object.prototype)
console.log(typeof obj.toString);       // "function" (from Object.prototype)

Prototype Chain Lookup

Property access walks the prototype chain until the property is found or the chain ends at null. This enables shared methods across all instances.

JavaScript
// The chain in action
const animal = {
    breathe() { return `${this.name} breathes`; }
};

const dog = {
    name: "Rex",
    bark() { return "Woof!"; }
};

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

console.log(dog.bark());    // "Woof!" (own method)
console.log(dog.breathe()); // "Rex breathes" (from prototype, this = dog)
console.log(dog.toString()); // "[object Object]" (from Object.prototype)
console.log(dog.nonexistent); // undefined (not found anywhere in chain)

// Visualizing the lookup:
// dog.breathe
//  -> not on dog
//  -> check dog's prototype (animal) -> found! returns animal.breathe
//     (called with this = dog because dog is the receiver)

// Override: own property shadows prototype property
dog.breathe = () => "dog breathes differently";
console.log(dog.breathe()); // "dog breathes differently" (own method wins)

Object.create()

Object.create(proto) creates a new object with the specified object as its prototype. It is the explicit way to set up prototype-based inheritance.

JavaScript
const vehicle = {
    type: "vehicle",
    describe() {
        return `${this.name} is a ${this.type}`;
    }
};

// Create car with vehicle as prototype
const car = Object.create(vehicle);
car.name = "Tesla";
car.type = "car"; // overrides prototype type

console.log(car.describe()); // "Tesla is a car"
console.log(Object.getPrototypeOf(car) === vehicle); // true

// Create with null prototype (no inherited properties)
const pure = Object.create(null);
pure.name = "pure object";
console.log(pure.toString); // undefined - no Object.prototype
console.log(Object.keys(pure)); // ["name"]

// Create with property descriptors
const point = Object.create(Object.prototype, {
    x: { value: 10, writable: true, enumerable: true, configurable: true },
    y: { value: 20, writable: true, enumerable: true, configurable: true }
});
console.log(point.x, point.y); // 10 20

Constructor Function Prototypes

Every function has a prototype property. When you use new, the new instance's [[Prototype]] is set to that prototype object.

JavaScript
function Animal(name) {
    this.name = name; // own property on each instance
}

// Add methods to the prototype (shared across all instances)
Animal.prototype.speak = function() {
    return `${this.name} makes a sound.`;
};

Animal.prototype.toString = function() {
    return `Animal(${this.name})`;
};

const cat = new Animal("Cat");
const dog = new Animal("Dog");

console.log(cat.speak()); // "Cat makes a sound."
console.log(dog.speak()); // "Dog makes a sound."

// Both instances share the same speak function (memory efficient)
console.log(cat.speak === dog.speak); // true (same reference!)

// Instance chain: cat -> Animal.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(cat) === Animal.prototype); // true
console.log(cat instanceof Animal); // true
console.log(cat.constructor === Animal); // true

Prototype-based Inheritance

JavaScript
// Parent constructor
function Animal(name) {
    this.name = name;
}
Animal.prototype.eat = function() {
    return `${this.name} is eating.`;
};

// Child constructor inheriting from Animal
function Dog(name, breed) {
    Animal.call(this, name);  // call parent constructor (sets this.name)
    this.breed = breed;
}

// Set up prototype chain: Dog -> Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // restore constructor reference

// Add Dog-specific methods
Dog.prototype.bark = function() {
    return `${this.name} barks!`;
};

const rex = new Dog("Rex", "German Shepherd");
console.log(rex.eat());   // "Rex is eating." (from Animal.prototype)
console.log(rex.bark());  // "Rex barks!"     (from Dog.prototype)
console.log(rex.name);    // "Rex"            (own property)
console.log(rex.breed);   // "German Shepherd" (own property)

console.log(rex instanceof Dog);    // true
console.log(rex instanceof Animal); // true

Own vs Inherited Properties

JavaScript
function Person(name) {
    this.name = name; // own property
}
Person.prototype.greet = function() { return `Hi, ${this.name}`; }; // prototype

const alice = new Person("Alice");

// Check own property
console.log(Object.hasOwn(alice, "name"));   // true (own)
console.log(Object.hasOwn(alice, "greet"));  // false (on prototype)

// for...in iterates BOTH own and inherited enumerable properties
for (const key in alice) {
    console.log(key); // "name", "greet"
}

// Filter to own only
for (const key in alice) {
    if (Object.hasOwn(alice, key)) {
        console.log(key); // "name" only
    }
}

// Object.keys - own enumerable only
console.log(Object.keys(alice));  // ["name"]
// Object.getOwnPropertyNames - own only (including non-enumerable)
console.log(Object.getOwnPropertyNames(alice)); // ["name"]

Classes as Syntactic Sugar

ES6 class syntax is syntactic sugar over the same prototype-based system. Under the hood, it creates the same constructor function and prototype chain.

JavaScript
// Class syntax (ES6+)
class Animal {
    constructor(name) {
        this.name = name;  // same as this.name in constructor function
    }
    eat() {                // goes on Animal.prototype
        return `${this.name} is eating.`;
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name);       // same as Animal.call(this, name)
        this.breed = breed;
    }
    bark() {              // goes on Dog.prototype
        return `${this.name} barks!`;
    }
}

const rex = new Dog("Rex", "Labrador");
console.log(rex.eat());  // "Rex is eating."
console.log(rex.bark()); // "Rex barks!"

// Same prototype chain as the function-based approach
console.log(Object.getPrototypeOf(rex) === Dog.prototype);    // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
console.log(typeof Animal); // "function" - classes are just functions!

Frequently Asked Questions

Every object in JavaScript has an internal link to another object called its prototype. When you access a property that does not exist on an object, JavaScript looks up the prototype chain: it checks the object, then its prototype, then that prototype's prototype, and so on until it reaches Object.prototype (the end of the chain, whose prototype is null). This is how array methods like push and map are found on all arrays.

__proto__ is the actual prototype link on an instance - the hidden internal slot pointing to the parent prototype object. prototype is a property on constructor functions and classes - it is the object that will become the __proto__ of any instance created with new. For example: const arr = [] - arr.__proto__ === Array.prototype. Use Object.getPrototypeOf(obj) instead of __proto__ in modern code.

Use ES6 class syntax - it is cleaner and less error-prone. Classes are syntactic sugar over prototype-based inheritance; they compile down to the same prototype chain. Direct prototype manipulation (MyClass.prototype.method = ...) is still useful for extending built-in objects or when working with legacy code, but for new code, class with extends is the standard.

Use Object.hasOwn(obj, "prop") (ES2022) or the older obj.hasOwnProperty("prop"). Both return true only if the property exists directly on the object, not on its prototype. The in operator ("prop" in obj) returns true for both own and inherited properties. Use hasOwn when iterating with for...in to skip inherited properties.