In JavaScript, prototypes are a key feature of the language’s object-oriented capabilities. JavaScript uses prototypal inheritance, which means that objects inherit properties and methods from other objects, rather than from classes as in classical inheritance. This is done through the object’s prototype.
What is a Prototype?
A prototype is an object from which other objects inherit properties. Every JavaScript object has a hidden internal property (called [[Prototype]]
), which can be accessed via __proto__
or using the Object.getPrototypeOf()
method. This prototype object serves as a blueprint, and objects can inherit methods and properties from it.
Prototype Chain
If you try to access a property on an object, JavaScript will first look at that object. If it doesn’t find the property, it will look at the object’s prototype, and then at the prototype’s prototype, and so on, forming a prototype chain. The search continues up the chain until the property is found or the chain ends at null
.
Example of Prototype Chain:
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // rabbit's prototype is now animal
console.log(rabbit.eats); // true (inherited from animal)
console.log(rabbit.jumps); // true (exists in rabbit)
In the above example:
- The
rabbit
object doesn’t have its owneats
property, but it inherits it from theanimal
prototype through the prototype chain.
Object Prototypes
Every object in JavaScript has a prototype, either set explicitly or inherited. For example, if you create an object using a constructor function or a class, it automatically inherits from the prototype of its constructor.
Example: Prototypes with Constructor Functions
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
let john = new Person("John", 30);
john.sayHello(); // Output: "Hello, my name is John"
In this example:
Person
is a constructor function.- We add a method
sayHello
toPerson.prototype
, so all instances ofPerson
(likejohn
) can access it via the prototype chain.
Prototype vs. __proto__
vs. prototype
It’s important to understand the difference between __proto__
and prototype
:
__proto__
: This is an internal property of an object that points to its prototype (the object it inherits from). All objects have__proto__
, but it’s not recommended to use this directly (useObject.getPrototypeOf()
instead).prototype
: This is a property of constructor functions (or classes) that points to the prototype object from which instances inherit methods and properties. This is where you define methods or properties that should be shared among all instances.
Example:
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.start = function() {
console.log(`${this.make} ${this.model} is starting...`);
};
let myCar = new Car("Toyota", "Corolla");
// Accessing prototype chain
console.log(myCar.__proto__ === Car.prototype); // true
// Calling a method inherited from the prototype
myCar.start(); // Output: "Toyota Corolla is starting..."
Car.prototype
refers to the object that instances ofCar
(likemyCar
) will inherit from.myCar.__proto__
refers to the same object, i.e.,Car.prototype
.
Prototype Inheritance
Objects can inherit properties from other objects via their prototype, allowing for shared behavior across multiple instances.
Example:
let animal = {
eats: true
};
let dog = Object.create(animal); // dog inherits from animal
dog.barks = true;
console.log(dog.eats); // true (inherited from animal)
console.log(dog.barks); // true (own property)
Here, dog
is created with animal
as its prototype using Object.create()
. The dog
object inherits properties from animal
, allowing access to the eats
property.
Prototype Methods
There are a number of useful built-in prototype methods in JavaScript for managing prototypes and inheritance:
Object.getPrototypeOf(obj)
: Returns the prototype of the given objectobj
.Object.setPrototypeOf(obj, prototype)
: Sets the prototype ofobj
to the givenprototype
.Object.create(proto)
: Creates a new object with the specified prototype.
Example:
let cat = {
meows: true
};
let myCat = Object.create(cat); // Create a new object with cat as the prototype
console.log(Object.getPrototypeOf(myCat) === cat); // true
Built-in Object Prototypes
All built-in JavaScript objects, like Arrays, Strings, Numbers, and so on, have prototypes that allow us to access methods like .toString()
, .push()
, .slice()
, etc. These methods are inherited from their respective prototype objects.
Example with Array Prototype:
let arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.push(4)); // Adds 4 to the array
The array arr
inherits from Array.prototype
, giving it access to array methods like .push()
, .slice()
, etc.
Modifying Built-in Prototypes
JavaScript allows developers to modify built-in object prototypes, though this is generally considered bad practice because it can lead to unexpected behavior.
Example of Modifying a Built-in Prototype:
String.prototype.reverse = function() {
return this.split('').reverse().join('');
};
console.log("hello".reverse()); // Output: "olleh"
In this case, we added a custom method reverse()
to the String.prototype
, allowing all strings to use this method. However, modifying built-in prototypes can lead to conflicts, especially in large projects or when using third-party libraries.
Benefits of Prototypes
- Memory Efficiency: Methods defined on prototypes are shared across all instances, reducing memory consumption compared to defining methods on each instance individually.
- Inheritance: Prototypes allow for easy inheritance of properties and methods between objects, enabling code reuse and shared behavior.
- Dynamic Extensions: Prototypes can be modified at runtime, allowing dynamic changes in behavior.
Limitations and Caution
- Modifying Built-in Prototypes: As mentioned earlier, modifying built-in prototypes (like
Array.prototype
orString.prototype
) is generally discouraged because it can lead to hard-to-debug errors. - Shadowing: Properties defined directly on an object will “shadow” properties with the same name on the prototype. The object will use its own property and ignore the prototype’s property unless it is deleted.
Example of Shadowing:
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
console.log("Woof!");
};
let myDog = new Dog("Fido");
myDog.bark(); // Output: "Woof!"
myDog.bark = function() {
console.log("Meow!"); // Overrides the prototype method
};
myDog.bark(); // Output: "Meow!" (shadowing the prototype method)
Summary
- Prototypes in JavaScript allow objects to inherit properties and methods from other objects, forming a prototype chain.
- Every object has a prototype, which is either inherited from another object or set explicitly.
__proto__
refers to the prototype of an object, whileprototype
is a property on constructor functions that defines the prototype for instances.- Prototypes enable JavaScript to implement inheritance and method sharing efficiently.
- Although powerful, modifying built-in prototypes should be done cautiously to avoid potential conflicts.
Prototypes are a crucial concept to master in order to fully understand JavaScript’s object-oriented nature and inheritance system.