Prototypes and Inheritance
Master JS prototype chains. Learn prototypes, custom inheritance patterns, classes, constructor calls, and object instantiation.
Downloaded from PrepIQ (https://prepiq.online)
Q1. What is the prototype chain in JavaScript?
Every JavaScript object has an internal link to another object called its prototype. When a property or method isn't found on the object itself, JavaScript looks up the chain of prototypes until it finds it or reaches null.
const animal = {
eats: true,
walk() {
console.log('Animal walks');
}
};
const rabbit = {
jumps: true,
__proto__: animal // rabbit's prototype is animal
};
console.log(rabbit.jumps); // true - own property
console.log(rabbit.eats); // true - found on prototype (animal)
rabbit.walk(); // 'Animal walks' - method found via prototype chain
// The chain can go multiple levels deep
const longEar = {
earsAreLong: true,
__proto__: rabbit
};
console.log(longEar.eats); // true - looked up: longEar -> rabbit -> animal
// The chain ends at Object.prototype, then null
console.log(animal.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null - end of the chain
// hasOwnProperty checks only the object's OWN properties, not inherited ones
console.log(rabbit.hasOwnProperty('jumps')); // true
console.log(rabbit.hasOwnProperty('eats')); // false - inherited, not own
// 'in' operator checks the whole chain
console.log('eats' in rabbit); // trueQ2. What is the difference between __proto__ and prototype?
| Property | Exists on | Purpose |
|---|---|---|
| prototype | Functions (specifically constructor functions/classes) | The object that will become the __proto__ of instances created with 'new' |
| __proto__ | Every object instance | The actual link to that object's prototype (accessor for the internal [[Prototype]]) |
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
const john = new Person('John');
// Person.prototype is the blueprint object
console.log(typeof Person.prototype); // 'object'
// john.__proto__ points to Person.prototype
console.log(john.__proto__ === Person.prototype); // true
// This is how john can access greet() even though it's not john's own property
console.log(john.hasOwnProperty('greet')); // false
console.log(john.greet()); // Hi, I'm John - found via __proto__ chain
// Modern way to get/set the prototype (preferred over direct __proto__ access)
console.log(Object.getPrototypeOf(john) === Person.prototype); // true
Object.setPrototypeOf(john, Person.prototype); // equivalent to john.__proto__ = ...
// Functions themselves have BOTH a prototype property AND a __proto__
console.log(typeof Person.prototype); // 'object' - used by 'new'
console.log(Person.__proto__ === Function.prototype); // true - Person is itself an object/function
// Only functions (used as constructors) have a meaningful .prototype property
console.log(john.prototype); // undefined - john is not a function
Q3. How does Object.create() work and how is it different from using 'new'?
Object.create() creates a new object with a specified prototype directly, without invoking a constructor function - giving explicit control over the prototype link.
// Object.create with an explicit prototype object
const animal = {
eats: true,
walk() {
console.log(`${this.name} is walking`);
}
};
const rabbit = Object.create(animal);
rabbit.name = 'Rabbit';
rabbit.jumps = true;
console.log(rabbit.eats); // true - inherited
rabbit.walk(); // Rabbit is walking
console.log(Object.getPrototypeOf(rabbit) === animal); // true
// Object.create(null) - object with NO prototype at all (not even Object.prototype)
const pureDict = Object.create(null);
pureDict.key = 'value';
console.log(pureDict.toString); // undefined - no inherited methods at all
// Useful for objects used purely as dictionaries/maps, avoiding prototype pollution
// Object.create with property descriptors (second argument)
const obj = Object.create(animal, {
name: { value: 'Bunny', writable: true, enumerable: true }
});
console.log(obj.name); // Bunny
// Comparison: using 'new' with a constructor function
function Animal(name) {
this.name = name;
}
Animal.prototype.eats = true;
const newAnimal = new Animal('Cat');
// 'new' does 4 things: 1) creates a new object, 2) sets its prototype to
// Animal.prototype, 3) calls Animal with 'this' bound to the new object,
// 4) returns the new object (unless the constructor returns its own object)
// Object.create is more direct - no constructor function/logic needed,
// just specify the prototype object explicitly
Q4. How do constructor functions and the 'new' keyword work together?
A constructor function is a regular function used with 'new' to create objects following a consistent template. The 'new' keyword performs several steps automatically.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}, ${this.age} years old`;
};
const john = new Person('John', 30);
console.log(john.greet()); // Hi, I'm John, 30 years old
// What 'new Person(...)' actually does step by step:
function simulateNew(Constructor, ...args) {
// 1. Create a new empty object with its prototype set to Constructor.prototype
const obj = Object.create(Constructor.prototype);
// 2. Call the constructor with 'this' bound to the new object
const result = Constructor.apply(obj, args);
// 3. If the constructor explicitly returns an object, use that instead
return (typeof result === 'object' && result !== null) ? result : obj;
}
const simulated = simulateNew(Person, 'Jane', 25);
console.log(simulated.greet()); // Hi, I'm Jane, 25 years old
// Common mistake: forgetting 'new'
function PersonNoNew(name) {
this.name = name; // 'this' is NOT bound to a new object in non-strict mode
}
const oops = PersonNoNew('Bob'); // called WITHOUT new
console.log(oops); // undefined - function returned nothing
// In strict mode or with classes, this throws a TypeError instead - safer
// ES6 classes enforce 'new' automatically
class PersonClass {
constructor(name) {
this.name = name;
}
}
// PersonClass('John'); // TypeError: Class constructor cannot be invoked without 'new'
// Checking if a function was called with 'new'
function SafeConstructor(name) {
if (!(this instanceof SafeConstructor)) {
throw new Error('Must be called with new');
}
this.name = name;
}
Q5. How do you implement inheritance using prototype-based patterns (before ES6 classes)?
Before ES6, inheritance was implemented by manually linking one constructor's prototype to another's, then calling the parent constructor explicitly.
// Parent constructor
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};
// Child constructor
function Dog(name, breed) {
Animal.call(this, name); // step 1: call parent constructor, bind 'this'
this.breed = breed;
}
// step 2: link Dog's prototype to a new object based on Animal's prototype
Dog.prototype = Object.create(Animal.prototype);
// step 3: fix the constructor reference (Object.create overwrote it)
Dog.prototype.constructor = Dog;
// step 4: add/override methods on the child's prototype
Dog.prototype.speak = function() {
return `${Animal.prototype.speak.call(this)}, specifically barking`;
};
const rex = new Dog('Rex', 'Labrador');
console.log(rex.speak()); // Rex makes a sound, specifically barking
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // true - inheritance chain works
// Common mistake: setting prototype directly instead of Object.create
function WrongDog(name) {
Animal.call(this, name);
}
WrongDog.prototype = Animal.prototype; // BAD - shares the exact same object
WrongDog.prototype.bark = function() { return 'Woof'; };
// This pollutes Animal.prototype too, affecting ALL Animal instances
// The ES6 class equivalent (syntactic sugar for the same pattern)
class AnimalES6 {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class DogES6 extends AnimalES6 {
constructor(name, breed) {
super(name);
this.breed = breed;
}
}
Q6. What is the difference between instance methods and static methods regarding prototypes?
| Type | Defined on | Called on | Accesses instance data? |
|---|---|---|---|
| Instance method | Constructor.prototype | Individual instances | Yes, via 'this' |
| Static method | The constructor/class itself | The class directly, not instances | No direct instance access |
function Circle(radius) {
this.radius = radius;
}
// Instance method - on the prototype, shared by all instances
Circle.prototype.getArea = function() {
return Math.PI * this.radius ** 2; // uses instance's own 'radius'
};
// Static method - directly on the constructor function
Circle.compare = function(circle1, circle2) {
return circle1.getArea() - circle2.getArea();
};
const c1 = new Circle(5);
const c2 = new Circle(3);
console.log(c1.getArea()); // 78.53... - instance method
console.log(Circle.compare(c1, c2)); // static method, called on Circle directly
// console.log(c1.compare); // undefined - static methods NOT accessible on instances
// ES6 class equivalent
class CircleES6 {
constructor(radius) {
this.radius = radius;
}
getArea() { // instance method - added to CircleES6.prototype
return Math.PI * this.radius ** 2;
}
static compare(c1, c2) { // static method - added directly to CircleES6
return c1.getArea() - c2.getArea();
}
static fromDiameter(diameter) { // common static use: factory methods
return new CircleES6(diameter / 2);
}
}
const circle = CircleES6.fromDiameter(10);
console.log(circle.radius); // 5
// Verifying where each method lives
console.log(CircleES6.prototype.hasOwnProperty('getArea')); // true
console.log(CircleES6.hasOwnProperty('compare')); // true
Q7. How does JavaScript handle multiple inheritance or shared behavior across unrelated classes?
JavaScript's prototype chain only supports single inheritance (one prototype per object), but mixins provide a pattern to share behavior across multiple unrelated classes.
// Mixin pattern - objects with reusable methods
const swimMixin = {
swim() {
return `${this.name} is swimming`;
}
};
const flyMixin = {
fly() {
return `${this.name} is flying`;
}
};
class Duck {
constructor(name) {
this.name = name;
}
}
// Apply mixins by copying methods onto the prototype
Object.assign(Duck.prototype, swimMixin, flyMixin);
const donald = new Duck('Donald');
console.log(donald.swim()); // Donald is swimming
console.log(donald.fly()); // Donald is flying
// Function-based mixins - more flexible, can be composed
const Serializable = Base => class extends Base {
toJSON() {
return JSON.stringify(this);
}
};
const Comparable = Base => class extends Base {
equals(other) {
return JSON.stringify(this) === JSON.stringify(other);
}
};
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
// Compose multiple mixins by wrapping the base class
class EnhancedPoint extends Comparable(Serializable(Point)) {}
const p1 = new EnhancedPoint(1, 2);
console.log(p1.toJSON()); // {"x":1,"y":2}
const p2 = new EnhancedPoint(1, 2);
console.log(p1.equals(p2)); // true
// Note: classes can only 'extends' ONE parent class directly
// class Foo extends A, B {} // SYNTAX ERROR - not allowed
Q8. What is the difference between Object.freeze(), Object.seal(), and Object.preventExtensions()?
| Method | Add new properties? | Delete properties? | Modify existing values? |
|---|---|---|---|
| Object.preventExtensions() | No | Yes | Yes |
| Object.seal() | No | No | Yes |
| Object.freeze() | No | No | No |
// Object.preventExtensions - blocks NEW properties only
const obj1 = { a: 1 };
Object.preventExtensions(obj1);
obj1.b = 2; // silently fails (or throws in strict mode)
obj1.a = 10; // works - existing properties can still change
delete obj1.a; // works - deletion still allowed
console.log(obj1); // {} - a was deleted successfully
// Object.seal - prevents adding/removing, but allows modifying existing values
const obj2 = { a: 1 };
Object.seal(obj2);
obj2.b = 2; // fails - can't add
obj2.a = 100; // works - can modify
delete obj2.a; // fails - can't delete
console.log(obj2); // { a: 100 }
console.log(Object.isSealed(obj2)); // true
// Object.freeze - most restrictive, object becomes fully immutable (shallow)
const obj3 = { a: 1, nested: { b: 2 } };
Object.freeze(obj3);
obj3.a = 100; // fails silently
obj3.c = 'new'; // fails silently
delete obj3.a; // fails silently
console.log(obj3.a); // 1 - unchanged
console.log(Object.isFrozen(obj3)); // true
// IMPORTANT: freeze is SHALLOW - nested objects are still mutable
obj3.nested.b = 999; // this WORKS, freeze doesn't apply recursively
console.log(obj3.nested.b); // 999
// Deep freeze helper
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).forEach(key => {
const value = obj[key];
if (value && typeof value === 'object') {
deepFreeze(value);
}
});
return Object.freeze(obj);
}
Q9. How does the 'this' keyword behave differently in prototype methods vs arrow functions defined as class fields?
Regular prototype methods have a dynamic 'this' that depends on how they're called, while arrow functions defined as class fields lexically bind 'this' to the instance at creation time.
class Counter {
count = 0;
// Regular method - added to Counter.prototype
// 'this' depends on HOW it's called
incrementRegular() {
this.count++;
}
// Arrow function class field - 'this' is bound lexically to the instance
// Created fresh for EACH instance (not shared on the prototype)
incrementArrow = () => {
this.count++;
};
}
const counter = new Counter();
// Calling directly - both work fine
counter.incrementRegular(); // this.count = 1, works
counter.incrementArrow(); // this.count = 2, works
// Problem case: passing method as a callback, losing the 'this' binding
const regularRef = counter.incrementRegular;
// regularRef(); // TypeError: Cannot read properties of undefined - 'this' is lost
const arrowRef = counter.incrementArrow;
arrowRef(); // works fine! 'this' remains bound to counter
// Common real-world scenario: event handlers or setTimeout
setTimeout(counter.incrementRegular, 100); // BROKEN - 'this' is undefined/global
setTimeout(counter.incrementArrow, 100); // WORKS - 'this' stays bound to counter
// The trade-off: arrow function fields create a NEW function per instance
// (memory cost), while regular methods are shared once on the prototype
console.log(Counter.prototype.incrementRegular); // exists on prototype
console.log(Counter.prototype.incrementArrow); // undefined - it's an instance property, not on prototype
Q10. How can you check an object's prototype chain and determine what an object inherits from?
JavaScript provides several methods to inspect the prototype chain, check inheritance relationships, and list inherited vs own properties.
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
bark() { return 'Woof'; }
}
const rex = new Dog('Rex');
// instanceof - checks if prototype exists anywhere in the chain
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // true - inherited through the chain
console.log(rex instanceof Object); // true - everything inherits from Object eventually
// Object.getPrototypeOf - walks up the chain manually
let proto = Object.getPrototypeOf(rex);
console.log(proto === Dog.prototype); // true
proto = Object.getPrototypeOf(proto);
console.log(proto === Animal.prototype); // true
proto = Object.getPrototypeOf(proto);
console.log(proto === Object.prototype); // true
// isPrototypeOf - checks if an object is anywhere in another's chain
console.log(Animal.prototype.isPrototypeOf(rex)); // true
// hasOwnProperty vs 'in' - distinguishing own vs inherited
console.log(rex.hasOwnProperty('name')); // true - set in constructor
console.log(rex.hasOwnProperty('speak')); // false - inherited method
console.log('speak' in rex); // true - 'in' checks the whole chain
// Object.getOwnPropertyNames - lists only OWN properties (not inherited)
console.log(Object.getOwnPropertyNames(rex)); // ['name']
// Listing all methods available via the prototype chain (for debugging)
function getAllMethods(obj) {
let methods = [];
let current = obj;
while (current) {
methods = methods.concat(Object.getOwnPropertyNames(current));
current = Object.getPrototypeOf(current);
}
return [...new Set(methods)];
}
console.log(getAllMethods(rex)); // includes name, bark, speak, constructor, etc.
Prototypes and Inheritance
Master JS prototype chains. Learn prototypes, custom inheritance patterns, classes, constructor calls, and object instantiation.
What is the prototype chain in JavaScript?
Every JavaScript object has an internal link to another object called its prototype. When a property or method...
What is the difference between __proto__ and prototype?
PropertyExists onPurposeprototypeFunctions (specifically constructor functions/classes)The object that will be...
How does Object.create() work and how is it different from using 'new'?
Object.create() creates a new object with a specified prototype directly, without invoking a constructor funct...
How do constructor functions and the 'new' keyword work together?
A constructor function is a regular function used with 'new' to create objects following a consistent template...
How do you implement inheritance using prototype-based patterns (before ES6 classes)?
Before ES6, inheritance was implemented by manually linking one constructor's prototype to another's, then cal...
What is the difference between instance methods and static methods regarding prototypes?
TypeDefined onCalled onAccesses instance data?Instance methodConstructor.prototypeIndividual instancesYes, via...
How does JavaScript handle multiple inheritance or shared behavior across unrelated classes?
JavaScript's prototype chain only supports single inheritance (one prototype per object), but mixins provide a...
What is the difference between Object.freeze(), Object.seal(), and Object.preventExtensions()?
MethodAdd new properties?Delete properties?Modify existing values?Object.preventExtensions()NoYesYesObject.sea...
How does the 'this' keyword behave differently in prototype methods vs arrow functions defined as class fields?
Regular prototype methods have a dynamic 'this' that depends on how they're called, while arrow functions defi...
How can you check an object's prototype chain and determine what an object inherits from?
JavaScript provides several methods to inspect the prototype chain, check inheritance relationships, and list...