Prototypes, Inheritance
JavaScript uses prototypal inheritance β objects can inherit properties from other objects.
Prototypal Inheritance
Every object has a hidden [[Prototype]] property that references another object (or null). When you access a property that doesnβt exist on the object, JavaScript looks it up on the prototype chain.
let animal = {
eats: true,
walk() {
console.log("Animal walks");
}
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // Set prototype (old way)
console.log(rabbit.eats); // β true (inherited from animal)
console.log(rabbit.jumps); // β true (own property)
rabbit.walk(); // β "Animal walks" (inherited)
The Prototype Chain
rabbit β animal β Object.prototype β null
let animal = { eats: true };
let rabbit = { jumps: true };
rabbit.__proto__ = animal;
// rabbit β animal β Object.prototype β null
console.log(rabbit.toString); // From Object.prototype
Writing to Prototype
Writing to a property only affects the object itself, not the prototype:
let animal = {
eats: true,
walk() {
console.log("Animal walks");
}
};
let rabbit = { __proto__: animal };
rabbit.walk = function() {
console.log("Rabbit hops!");
};
rabbit.walk(); // β "Rabbit hops!" (own method)
animal.walk(); // β "Animal walks" (unchanged)
βthisβ in Prototypes
The value of this is always the object before the dot, even if the method is on the prototype:
let animal = {
name: "Animal",
walk() {
console.log(this.name + " walks");
}
};
let rabbit = {
__proto__: animal,
name: "Rabbit"
};
rabbit.walk(); // β "Rabbit walks" (this = rabbit)
forβ¦in and Prototypes
for...in iterates over own + inherited enumerable properties:
let animal = { eats: true };
let rabbit = { jumps: true, __proto__: animal };
for (let key in rabbit) {
let isOwn = rabbit.hasOwnProperty(key);
console.log(`${key}: ${isOwn ? "own" : "inherited"}`);
}
// β "jumps: own"
// β "eats: inherited"
F.prototype
The F.prototype property (note: not [[Prototype]]) is used with the new operator.
function Animal(name) {
this.name = name;
}
// Animal.prototype is the object that becomes [[Prototype]] of new instances
Animal.prototype.walk = function() {
console.log(this.name + " walks");
};
let rabbit = new Animal("Rabbit");
let cat = new Animal("Cat");
rabbit.walk(); // β "Rabbit walks"
cat.walk(); // β "Cat walks"
// Both share the same walk method via prototype
console.log(rabbit.walk === cat.walk); // β true
How it works
When new Animal() is called:
- A new object
{}is created - Its
[[Prototype]]is set toAnimal.prototype - The constructor runs with
thisset to the new object
// Animal.prototype is just a regular object:
console.log(Animal.prototype); // β { walk: [Function], constructor: Animal }
console.log(Animal.prototype.constructor === Animal); // β true
Default F.prototype
Every function automatically has a prototype property pointing to an object with a single property constructor:
function Foo() {}
console.log(Foo.prototype.constructor === Foo); // β true
let obj = new Foo();
console.log(obj.constructor === Foo); // β true (inherited)
Change F.prototype
You can replace the prototype to provide a different set of methods:
function Rabbit() {}
// Replace the prototype entirely
Rabbit.prototype = {
jumps: true
};
// But now constructor is gone!
let r = new Rabbit();
console.log(r.constructor === Rabbit); // β false
// Fix it:
Rabbit.prototype = {
jumps: true,
constructor: Rabbit // Explicitly set constructor
};
Native Prototypes
Built-in objects like Array, String, Number all use prototypes.
// Array methods are on Array.prototype
let arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // β true
// String methods are on String.prototype
let str = "hello";
console.log(str.__proto__ === String.prototype); // β true
The Complete Chain
let arr = [1, 2, 3];
// arr β Array.prototype β Object.prototype β null
console.log(arr.__proto__ === Array.prototype); // β true
console.log(arr.__proto__.__proto__ === Object.prototype); // β true
console.log(arr.__proto__.__proto__.__proto__); // β null
Modifying Native Prototypes (Not Recommended)
// β This works but DON'T do it in production
String.prototype.show = function() {
console.log(this);
};
"hello".show(); // β "hello" (but could conflict with future JS versions)
Polyfills (this is OK)
Adding methods to native prototypes is acceptable for polyfills:
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(str) {
return this.indexOf(str) === 0;
};
}
Prototype Methods, Objects without proto
Modern ways to work with prototypes:
Modern Methods
let animal = { eats: true };
let rabbit = { jumps: true };
// Get/set prototype
console.log(Object.getPrototypeOf(rabbit)); // β {} (default)
Object.setPrototypeOf(rabbit, animal);
console.log(Object.getPrototypeOf(rabbit)); // β animal
// Create object with given prototype
let rabbit2 = Object.create(animal);
rabbit2.jumps = true;
Object.create
Create objects with a specific prototype and property descriptors:
let animal = { eats: true };
// Simple
let rabbit = Object.create(animal);
rabbit.jumps = true;
// With property descriptors
let rabbit2 = Object.create(animal, {
jumps: {
value: true,
writable: true,
enumerable: true,
configurable: true
}
});
Object.create for Object Copying
let clone = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
Why avoid proto?
- Itβs a getter/setter on
Object.prototype, which can cause performance issues - Not supported in all environments (though modern ones do)
- The methods
getPrototypeOf/setPrototypeOfare the proper way
Objects without Prototype
Create a truly plain object (no prototype chain):
let obj = Object.create(null);
// obj has NO prototype β no toString, no hasOwnProperty, etc.
obj.toString; // β undefined (no Object.prototype)
console.log(Object.getPrototypeOf(obj)); // β null
// Useful for pure dictionaries (no key collisions)
let dict = Object.create(null);
dict["toString"] = "value"; // No collision with Object.prototype.toString
// Inheritance chain
let animal = {
eats: true,
sleep() {
return this.name + ' sleeps';
}
};
let rabbit = Object.create(animal);
rabbit.jumps = true;
rabbit.name = 'Buggs';
document.write('Eats: ' + rabbit.eats + '<br>');
document.write('Jumps: ' + rabbit.jumps + '<br>');
document.write(rabbit.sleep() + '<br>');
// Prototype chain
document.write('<br>Prototype chain:<br>');
document.write('rabbit β animal: ' + (Object.getPrototypeOf(rabbit) === animal) + '<br>');
document.write('animal β Object.prototype: ' + (Object.getPrototypeOf(animal) === Object.prototype) + '<br>');
document.write('Object.prototype β null: ' + (Object.getPrototypeOf(Object.prototype) === null) + '<br>');
// hasOwnProperty
document.write('<br>Own vs inherited:<br>');
for (let key in rabbit) {
if (Object.hasOwn(rabbit, key)) {
document.write(key + ': own<br>');
} else {
document.write(key + ': inherited<br>');
}
}