Prototypes and Inheritance
JavaScript uses prototype-based inheritance where objects can inherit directly from other objects.
Understanding Prototypes
javascript// Every function has a prototype property function Person(name, age) { this.name = name; this.age = age; } // Add methods to prototype Person.prototype.greet = function() { return `Hello, I'm ${this.name}`; }; Person.prototype.getAge = function() { return this.age; }; // Create instances const person1 = new Person('Alice', 30); const person2 = new Person('Bob', 25); console.log(person1.greet()); // Hello, I'm Alice console.log(person2.greet()); // Hello, I'm Bob // Both instances share the same prototype methods console.log(person1.greet === person2.greet); // true
Prototype Chain
javascript// Check prototype relationships console.log(person1.__proto__ === Person.prototype); // true console.log(Person.prototype.__proto__ === Object.prototype); // true console.log(Object.prototype.__proto__); // null // hasOwnProperty vs inherited properties person1.species = 'Human'; console.log(person1.hasOwnProperty('name')); // true (own property) console.log(person1.hasOwnProperty('greet')); // false (inherited) console.log('greet' in person1); // true (inherited property exists) // Object.getPrototypeOf() console.log(Object.getPrototypeOf(person1) === Person.prototype); // true // Check if object is instance of constructor console.log(person1 instanceof Person); // true console.log(person1 instanceof Object); // true
Prototype Inheritance
javascript// Parent constructor function Animal(name, species) { this.name = name; this.species = species; } Animal.prototype.makeSound = function() { return 'Some generic sound'; }; Animal.prototype.info = function() { return `${this.name} is a ${this.species}`; }; // Child constructor function Dog(name, breed) { Animal.call(this, name, 'Dog'); // Call parent constructor this.breed = breed; } // Set up inheritance Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog; // Override parent method Dog.prototype.makeSound = function() { return 'Woof! Woof!'; }; // Add new method Dog.prototype.wagTail = function() { return `${this.name} is wagging tail!`; }; const dog = new Dog('Buddy', 'Golden Retriever'); console.log(dog.info()); // Buddy is a Dog console.log(dog.makeSound()); // Woof! Woof! console.log(dog.wagTail()); // Buddy is wagging tail!
Object.create() and Modern Patterns
javascript// Create object with specific prototype const personPrototype = { greet() { return `Hello, I'm ${this.name}`; }, setAge(age) { this.age = age; } }; // Create object with personPrototype as prototype const person = Object.create(personPrototype); person.name = 'Charlie'; person.age = 35; console.log(person.greet()); // Hello, I'm Charlie // Factory function pattern function createPerson(name, age) { const person = Object.create(personPrototype); person.name = name; person.age = age; return person; } const person3 = createPerson('Diana', 28); console.log(person3.greet()); // Hello, I'm Diana // Prototype delegation const vehicle = { init(make, model) { this.make = make; this.model = model; return this; }, start() { return `${this.make} ${this.model} is starting`; } }; const car = Object.create(vehicle); car.init('Toyota', 'Camry'); console.log(car.start()); // Toyota Camry is starting
Prototype Manipulation
javascript// Adding methods to built-in prototypes (use with caution) String.prototype.capitalize = function() { return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase(); }; console.log('hello world'.capitalize()); // Hello world Array.prototype.last = function() { return this[this.length - 1]; }; console.log([1, 2, 3, 4].last()); // 4 // Checking and modifying prototypes function User(name) { this.name = name; } User.prototype.getName = function() { return this.name; }; const user = new User('John'); // Add method to prototype after object creation User.prototype.setName = function(name) { this.name = name; }; user.setName('Jane'); // Works on existing instances console.log(user.getName()); // Jane // Object.setPrototypeOf() (not recommended for performance) const newPrototype = { newMethod() { return 'New method called'; } }; Object.setPrototypeOf(user, newPrototype); console.log(user.newMethod()); // New method called
Practical Examples
javascript// Event emitter using prototypes function EventEmitter() { this.events = {}; } EventEmitter.prototype.on = function(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); }; EventEmitter.prototype.emit = function(event, ...args) { if (this.events[event]) { this.events[event].forEach(callback => callback(...args)); } }; EventEmitter.prototype.off = function(event, callback) { if (this.events[event]) { this.events[event] = this.events[event].filter(cb => cb !== callback); } }; // Usage const emitter = new EventEmitter(); emitter.on('data', (data) => console.log('Received:', data)); emitter.emit('data', 'Hello World'); // Prototype-based module pattern const Calculator = (function() { function Calculator() { this.result = 0; } Calculator.prototype.add = function(num) { this.result += num; return this; }; Calculator.prototype.subtract = function(num) { this.result -= num; return this; }; Calculator.prototype.multiply = function(num) { this.result *= num; return this; }; Calculator.prototype.divide = function(num) { if (num !== 0) { this.result /= num; } return this; }; Calculator.prototype.getValue = function() { return this.result; }; Calculator.prototype.reset = function() { this.result = 0; return this; }; return Calculator; })(); // Usage with method chaining const calc = new Calculator(); const result = calc.add(10).multiply(2).subtract(5).getValue(); console.log(result); // 15
Prototype vs Class Comparison
javascript// Prototype-based approach function PersonProto(name, age) { this.name = name; this.age = age; } PersonProto.prototype.greet = function() { return `Hello, I'm ${this.name}`; }; // Class-based approach (ES6) class PersonClass { constructor(name, age) { this.name = name; this.age = age; } greet() { return `Hello, I'm ${this.name}`; } } // Both create similar objects const protoInstance = new PersonProto('Alice', 30); const classInstance = new PersonClass('Bob', 25); console.log(protoInstance.greet()); // Hello, I'm Alice console.log(classInstance.greet()); // Hello, I'm Bob // Classes are syntactic sugar over prototypes console.log(typeof PersonClass); // function console.log(PersonClass.prototype.greet); // function // Performance considerations console.time('Prototype creation'); for (let i = 0; i < 100000; i++) { new PersonProto(`Person${i}`, i); } console.timeEnd('Prototype creation'); console.time('Class creation'); for (let i = 0; i < 100000; i++) { new PersonClass(`Person${i}`, i); } console.timeEnd('Class creation');