Skip to main content
โšก Calmops

Prototypes and Prototype Chain

Prototypes and Prototype Chain

Prototypes are the mechanism by which JavaScript objects inherit features from one another. Understanding prototypes is essential for JavaScript OOP.

What is a Prototype?

Every JavaScript object has a prototype, which is another object:

const obj = {};
console.log(Object.getPrototypeOf(obj)); // Object.prototype
console.log(obj.__proto__); // Object.prototype (deprecated but still works)

Prototype Chain

Objects inherit properties from their prototype, which can inherit from its prototype:

const grandparent = { name: "Grandparent" };
const parent = Object.create(grandparent);
parent.age = 50;

const child = Object.create(parent);
child.hobby = "coding";

console.log(child.hobby); // "coding" (own property)
console.log(child.age); // 50 (from parent)
console.log(child.name); // "Grandparent" (from grandparent)

Object.create()

Create an object with a specific prototype:

const proto = {
    greet() {
        return `Hello, ${this.name}!`;
    }
};

const person = Object.create(proto);
person.name = "Alice";

console.log(person.greet()); // "Hello, Alice!"

Constructor Functions

Functions used with new keyword to create objects:

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

Person.prototype.greet = function() {
    return `Hello, I'm ${this.name}`;
};

const alice = new Person("Alice", 30);
console.log(alice.greet()); // "Hello, I'm Alice"

How new Works

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

// new does this:
// 1. Create empty object
// 2. Set object's prototype to Person.prototype
// 3. Call Person with this = object
// 4. Return object

const person = new Person("Alice");

Prototype Methods

Object.getPrototypeOf()

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

const dog = new Animal("Buddy");
console.log(Object.getPrototypeOf(dog) === Animal.prototype); // true

Object.setPrototypeOf()

const proto = { greet() { return "Hello"; } };
const obj = {};

Object.setPrototypeOf(obj, proto);
console.log(obj.greet()); // "Hello"

Object.getOwnPropertyNames()

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

Person.prototype.age = 30;

const person = new Person("Alice");
console.log(Object.getOwnPropertyNames(person)); // ["name"]
console.log(person.age); // 30 (from prototype)

Checking Prototype

hasOwnProperty()

const person = Object.create({ age: 30 });
person.name = "Alice";

console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("age")); // false

in Operator

const person = Object.create({ age: 30 });
person.name = "Alice";

console.log("name" in person); // true
console.log("age" in person); // true

instanceof

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

const dog = new Animal("Buddy");
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

Prototype Inheritance

Single Inheritance

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

Animal.prototype.speak = function() {
    return `${this.name} makes a sound`;
};

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
    return `${this.name} barks`;
};

const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.speak()); // "Buddy makes a sound"
console.log(dog.bark()); // "Buddy barks"

Prototype Chain Example

function Vehicle(brand) {
    this.brand = brand;
}

Vehicle.prototype.start = function() {
    return `${this.brand} started`;
};

function Car(brand, model) {
    Vehicle.call(this, brand);
    this.model = model;
}

Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;

Car.prototype.drive = function() {
    return `${this.brand} ${this.model} is driving`;
};

const car = new Car("Toyota", "Camry");
console.log(car.start()); // "Toyota started"
console.log(car.drive()); // "Toyota Camry is driving"

Practical Examples

Extending Built-in Objects

// Add method to Array prototype
Array.prototype.first = function() {
    return this[0];
};

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

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

Creating a Plugin System

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

Plugin.prototype.execute = function() {
    return `${this.name} executed`;
};

function AuthPlugin(name) {
    Plugin.call(this, name);
}

AuthPlugin.prototype = Object.create(Plugin.prototype);
AuthPlugin.prototype.constructor = AuthPlugin;

AuthPlugin.prototype.authenticate = function(user) {
    return `${this.name} authenticated ${user}`;
};

const auth = new AuthPlugin("Auth");
console.log(auth.execute()); // "Auth executed"
console.log(auth.authenticate("Alice")); // "Auth authenticated Alice"

Mixin Pattern

const canEat = {
    eat() { return `${this.name} is eating`; }
};

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

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

// Mix in methods
Object.assign(Person.prototype, canEat, canWalk);

const person = new Person("Alice");
console.log(person.eat()); // "Alice is eating"
console.log(person.walk()); // "Alice is walking"

Common Patterns

Prototype Delegation

const parent = {
    greet() { return "Hello"; }
};

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

console.log(child.greet()); // "Hello"

Concatenative Inheritance

function createAnimal(name) {
    return {
        name,
        speak() { return `${this.name} speaks`; }
    };
}

function createDog(name, breed) {
    const animal = createAnimal(name);
    return Object.assign(animal, {
        breed,
        bark() { return `${this.name} barks`; }
    });
}

const dog = createDog("Buddy", "Golden");
console.log(dog.speak()); // "Buddy speaks"
console.log(dog.bark()); // "Buddy barks"

Performance Considerations

Prototype Lookup

// Prototype lookup is slower than own properties
const obj = { a: 1 };
Object.setPrototypeOf(obj, { b: 2 });

// Accessing obj.a is faster than obj.b
// because obj.a is an own property

Avoid Modifying Built-in Prototypes

// Bad - modifies built-in prototype
Array.prototype.myMethod = function() { };

// Good - create your own class
class MyArray extends Array {
    myMethod() { }
}

Summary

  • Prototype: object that other objects inherit from
  • Prototype chain: chain of prototypes for inheritance
  • Constructor function: function used with new keyword
  • Object.create(): create object with specific prototype
  • Inheritance: set up with Object.create() and call()
  • hasOwnProperty(): check if property is own
  • instanceof: check if object is instance of constructor
  • Best practice: use classes instead of prototypes (ES6+)

Official Documentation

Next Steps

  1. Constructor Functions and the ’new’ Keyword
  2. ES6 Classes: Syntax and Features
  3. Inheritance: Extends and Super

Comments