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
newkeyword - 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+)
Related Resources
Official Documentation
Next Steps
- Constructor Functions and the ’new’ Keyword
- ES6 Classes: Syntax and Features
- Inheritance: Extends and Super
Comments