JavaScript tutorials > Object-Oriented JavaScript > Classes and Prototypes > How do you inherit properties in JavaScript?
How do you inherit properties in JavaScript?
Inheritance in JavaScript is a powerful mechanism that allows you to create new objects based on existing ones, inheriting their properties and methods. This promotes code reusability and helps organize your code in a more maintainable way. This tutorial explores different approaches to inheritance, focusing on prototypal inheritance and class-based inheritance introduced in ES6.
Prototypal Inheritance: The Foundation
This example demonstrates prototypal inheritance using constructor functions and the `Object.create()` method. First, we define an `Animal` constructor with a `name` property and a `sayName` method added to its prototype. Then, we define a `Dog` constructor that calls the `Animal` constructor using `Animal.call(this, name)` to initialize the `name` property. Crucially, `Dog.prototype = Object.create(Animal.prototype)` creates a new object that inherits from `Animal.prototype` and assigns it to `Dog.prototype`. This establishes the inheritance relationship. Finally, we reset `Dog.prototype.constructor` to point back to the `Dog` constructor and add a `bark` method to `Dog.prototype`. The `instanceof` operator checks if an object is an instance of a constructor and also if it's an instance of any of its parent constructors.
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function() {
return 'My name is ' + this.name;
};
function Dog(name, breed) {
Animal.call(this, name); // Call the parent constructor
this.breed = breed;
}
// Set up the prototype chain: Dog inherits from Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // Reset constructor property
Dog.prototype.bark = function() {
return 'Woof!';
};
const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.sayName()); // Output: My name is Buddy
console.log(myDog.bark()); // Output: Woof!
console.log(myDog instanceof Animal); //true
console.log(myDog instanceof Dog); //true
ES6 Classes: Syntactic Sugar
ES6 classes provide a more convenient syntax for prototypal inheritance. The `class` keyword defines a class, and the `extends` keyword specifies the class to inherit from. The `super()` method calls the parent class's constructor. Underneath the hood, ES6 classes still use prototypal inheritance; they just provide a cleaner and more familiar syntax for developers coming from other object-oriented languages. This allows easier organization of code.
class Animal {
constructor(name) {
this.name = name;
}
sayName() {
return `My name is ${this.name}`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call the parent constructor
this.breed = breed;
}
bark() {
return 'Woof!';
}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.sayName()); // Output: My name is Buddy
console.log(myDog.bark()); // Output: Woof!
console.log(myDog instanceof Animal); //true
console.log(myDog instanceof Dog); //true
Concepts behind the snippet
The key concept behind inheritance is code reusability. Instead of rewriting common functionality in each class, you can define it once in a parent class and have child classes inherit it. This reduces redundancy and makes your code more maintainable. Understanding the prototype chain is crucial for grasping how inheritance works in JavaScript. The prototype chain is a series of objects that are linked together, where each object inherits properties and methods from the object in its prototype. Every object in JavaScript (except the root object) has a prototype, and that prototype is also an object, and it has a prototype of its own, and so on. This continues until we reach an object that has null as its prototype. The end of this chain of prototypes is known as the null prototype, and all objects ultimately inherit from it.
Real-Life Use Case Section
Consider a scenario where you are building a game with different types of characters. You might have a base `Character` class with properties like `health`, `attackPower`, and `move`. Then, you can create subclasses like `Warrior`, `Mage`, and `Archer` that inherit from `Character` and add their own unique properties and methods. For example, `Warrior` might have a `specialAttack` method, while `Mage` might have a `castSpell` method. In web applications, consider a `Button` component. You can have a base `Button` class, and then subclasses for specific button types like `PrimaryButton`, `SecondaryButton`, etc., each inheriting the basic button functionality but with different styling.
Best Practices
Interview Tip
Be prepared to explain the difference between prototypal inheritance and classical inheritance (as found in languages like Java or C++). Also, be ready to discuss the pros and cons of inheritance and when composition might be a better choice. A common interview question is to implement inheritance using both prototypal inheritance and ES6 classes.
When to use them
Use inheritance when you have a clear 'is-a' relationship between objects. For example, a `Dog` 'is-a' `Animal`. Also, use inheritance when you want to reuse code and avoid duplication. When the relationship is more of 'has-a' instead of 'is-a' consider using composition, where one object contains another.
Memory footprint
Inheritance can potentially increase the memory footprint, especially with deep hierarchies. Each object inherits properties and methods from its parent classes, which can add to the object's size. However, methods are typically shared via the prototype, so they don't contribute significantly to individual object sizes. Carefully consider the complexity of your inheritance hierarchies and whether the benefits of code reuse outweigh the potential memory overhead. Modern JavaScript engines are generally efficient in handling inheritance, but it's still a factor to be aware of, especially in resource-constrained environments.
Alternatives
Besides inheritance, you can use:
Composition is generally preferred over inheritance because it leads to more flexible and maintainable code.
Pros
Cons
FAQ
-
What is the prototype chain?
The prototype chain is a mechanism in JavaScript where objects inherit properties and methods from other objects. Each object has a prototype, and that prototype also has a prototype, and so on, until reaching `null`. When trying to access a property or method of an object, JavaScript first looks for it on the object itself. If it's not found, it looks at the object's prototype, then the prototype's prototype, and so on, up the chain. -
How do I prevent inheritance?
In JavaScript, there's no direct way to completely prevent inheritance for existing object structures. However, you can achieve a similar effect by:- Object.freeze(): This method freezes an object, preventing new properties from being added and existing properties from being modified or deleted. However, properties that are objects themselves can still be modified.
- Object.seal(): This method seals an object, preventing new properties from being added or existing properties from being deleted. Existing properties can still be modified.
-
Why is `Dog.prototype.constructor = Dog` necessary?
When you set `Dog.prototype = Object.create(Animal.prototype)`, you are replacing the original `Dog.prototype` object with a new object that inherits from `Animal.prototype`. The original `Dog.prototype` object had its `constructor` property pointing to the `Dog` function. The new `Dog.prototype` object inherits its `constructor` property from `Animal.prototype`, so it now points to the `Animal` function. Setting `Dog.prototype.constructor = Dog` resets the `constructor` property to point back to the `Dog` function, which is important for accurately determining the constructor of `Dog` instances.