Problem Statement
Explain the prototype chain in JavaScript. How does property lookup work?
Explanation
The prototype chain is the mechanism JavaScript uses to implement inheritance and property lookup.
How it works:
Every object has an internal prototype link that points to another object. This creates a chain of objects. When you access a property, JavaScript searches up this chain until found or reaching null. Null marks the end of the chain.
Property lookup process:
Step 1: Check if property exists directly on the object.
Step 2: If not found, check the object's prototype.
Step 3: If not found, check the prototype's prototype.
Step 4: Continue until property is found or reaching null.
Step 5: If null is reached, return undefined.
The chain structure:
All objects ultimately inherit from Object.prototype. Arrays inherit from Array.prototype which inherits from Object.prototype. Functions inherit from Function.prototype which inherits from Object.prototype. The chain always ends with Object.prototype whose prototype is null.
Methods on the prototype:
Methods shared by all instances live on the prototype. This saves memory compared to copying methods to each instance. Changes to prototype affect all existing instances.
Common gotchas:
Adding properties to an object does not affect its prototype. Modifying prototype affects all objects using that prototype. Performance decreases with longer chains. Can accidentally overwrite prototype methods.
Accessing prototypes:
Use Object.getPrototypeOf for reading. Use Object.setPrototypeOf for setting, though this is slow. Use underscore underscore proto underscore for convenience, though deprecated. Use instanceof to check prototype chain.
Understanding the prototype chain is fundamental to JavaScript and frequently tested in interviews.
Code Solution
SolutionRead Only
// BASIC PROTOTYPE CHAIN
const obj = {};
// obj -> Object.prototype -> null
console.log(Object.getPrototypeOf(obj)); // Object.prototype
console.log(Object.getPrototypeOf(Object.prototype)); // null
// PROPERTY LOOKUP DEMONSTRATION
function Person(name) {
this.name = name; // Own property
}
Person.prototype.greet = function() {
console.log('Hello ' + this.name);
}; // Prototype property
const john = new Person('John');
// Lookup for 'name'
// 1. Check john object - FOUND
console.log(john.name); // 'John'
// Lookup for 'greet'
// 1. Check john object - not found
// 2. Check Person.prototype - FOUND
john.greet(); // 'Hello John'
// Lookup for 'toString'
// 1. Check john object - not found
// 2. Check Person.prototype - not found
// 3. Check Object.prototype - FOUND
console.log(john.toString()); // '[object Object]'
// Lookup for 'nonExistent'
// 1. Check john object - not found
// 2. Check Person.prototype - not found
// 3. Check Object.prototype - not found
// 4. Reach null - return undefined
console.log(john.nonExistent); // undefined
// VISUALIZING THE CHAIN
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log('Eating');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
const buddy = new Dog('Buddy', 'Labrador');
// Prototype chain:
// buddy -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
// Verify chain
console.log(Object.getPrototypeOf(buddy) === Dog.prototype); // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
// Property lookup on buddy
buddy.bark(); // Found on Dog.prototype
buddy.eat(); // Found on Animal.prototype
console.log(buddy.toString()); // Found on Object.prototype
// SHADOWING PROPERTIES
const parent = {
value: 10
};
const child = Object.create(parent);
console.log(child.value); // 10 (from parent)
child.value = 20; // Creates own property (shadows parent)
console.log(child.value); // 20 (own property)
console.log(parent.value); // 10 (unchanged)
delete child.value; // Remove own property
console.log(child.value); // 10 (back to parent)
// PERFORMANCE IMPLICATIONS
// Short chain (fast)
const obj1 = {x: 1};
console.log(obj1.x); // Quick lookup
// Long chain (slower)
const level1 = {a: 1};
const level2 = Object.create(level1);
const level3 = Object.create(level2);
const level4 = Object.create(level3);
const level5 = Object.create(level4);
console.log(level5.a); // Slower (walks through 4 prototypes)
// COMMON METHODS ON OBJECT.PROTOTYPE
const anyObj = {};
// These are all inherited from Object.prototype
console.log(anyObj.toString()); // '[object Object]'
console.log(anyObj.hasOwnProperty('x')); // false
console.log(anyObj.valueOf()); // {}
// CHECKING THE CHAIN
console.log(buddy instanceof Dog); // true
console.log(buddy instanceof Animal); // true
console.log(buddy instanceof Object); // true
// Using in operator (checks entire chain)
console.log('bark' in buddy); // true
console.log('eat' in buddy); // true
// Using hasOwnProperty (only own properties)
console.log(buddy.hasOwnProperty('name')); // true
console.log(buddy.hasOwnProperty('bark')); // false
console.log(buddy.hasOwnProperty('eat')); // false
