In object-oriented programming, method overriding is a powerful feature that allows a subclass or child object to provide a specific implementation of a method that is already defined in its parent or superclass. This means that when the method is called on an instance of the subclass, the subclass’s version of the method runs instead of the parent’s. Overriding enables programmers to change or extend the behavior inherited from a parent class, making objects more flexible and specialized without changing the parent code.
JavaScript supports method overriding in multiple ways, thanks to its flexible prototype-based inheritance system and modern class syntax. Understanding method overriding is crucial to mastering how inheritance works in JavaScript, as it allows different objects to behave uniquely even when they share the same method names. This article will guide you step-by-step through practical examples of method overriding in JavaScript, starting with ES6 classes and then moving to constructor functions, prototypes, and object literals.
Method Overriding with ES6 Classes
ES6 introduced a clean and modern way to define classes in JavaScript, making it straightforward to override methods. Let’s start by defining a simple parent class called Vehicle
with a method named move
.
class Vehicle {
move() {
console.log("The vehicle moves forward.");
}
}
class Car extends Vehicle {
move() {
console.log("The car drives on the road.");
}
}
const myVehicle = new Vehicle();
myVehicle.move(); // Output: The vehicle moves forward.
const myCar = new Car();
myCar.move(); // Output: The car drives on the road.
In this example, the Vehicle
class has a method move()
which prints a generic message. The subclass Car
overrides this method with its own version. When move()
is called on myCar
, the overridden method runs, showing the specialized behavior for cars. This is a clear demonstration of how method overriding works with ES6 classes.
Calling the Parent Method Inside the Overridden Method
Sometimes, you want the child class method to extend the behavior of the parent rather than completely replacing it. JavaScript provides the super
keyword to call the parent class’s methods inside an overridden method.
Here’s how it works:
class Vehicle {
move() {
console.log("The vehicle moves forward.");
}
}
class Car extends Vehicle {
move() {
super.move();
console.log("The car drives on the road with style!");
}
}
const myCar = new Car();
myCar.move();
// Output:
// The vehicle moves forward.
// The car drives on the road with style!
In this case, Car
overrides the move()
method but calls super.move()
first to execute the original Vehicle
method. Then it adds its own message. This pattern is helpful when you want to keep the base behavior while adding extra functionality.
Method Overriding with Constructor Functions and Prototypes
Before ES6, JavaScript used constructor functions and prototypes to achieve object-oriented behavior. Method overriding is still possible here by redefining methods on the prototype of the child constructor.
Consider this example with animals:
function Animal() {}
Animal.prototype.speak = function() {
console.log("The animal makes a sound.");
};
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
console.log("The dog barks.");
};
const genericAnimal = new Animal();
genericAnimal.speak(); // Output: The animal makes a sound.
const myDog = new Dog();
myDog.speak(); // Output: The dog barks.
Here, Animal
has a speak()
method defined on its prototype. Dog
inherits from Animal
by setting its prototype chain, then overrides speak()
with a new implementation. When speak()
is called on a Dog
instance, the overridden method is executed, showing the polymorphic behavior.
Overriding Methods in Object Literals Using Object.create()
JavaScript’s prototype system allows objects to inherit directly from other objects, not just from classes or constructors. You can override methods by creating objects with a prototype and redefining methods in the child object.
Let’s see an example with shapes:
const Shape = {
draw() {
console.log("Drawing a shape.");
},
test() {
console.log("Test method.");
}
};
const Circle = Object.create(Shape);
Circle.draw = function() {
console.log("Drawing a circle.");
};
Shape.draw(); // Output: Drawing a shape.
Shape.test(); // Output: Test method.
Circle.draw(); // Output: Drawing a circle.
Circle.test(); // Output: Test method.
Here, Circle
inherits from Shape
. The draw()
method is overridden in Circle
by redefining it. Calling draw()
on Circle
runs the new method, while Shape.draw()
remains unchanged.
Fun Example: Overriding Methods in a Game Character Hierarchy
To make the concept more engaging, imagine a game with different character classes. We will create a base Character
class with an attack()
method. Each subclass will override attack()
to perform unique actions.
class Character {
attack() {
console.log("The character attacks!");
}
}
class Wizard extends Character {
attack() {
console.log("The wizard casts a fireball!");
}
}
class Knight extends Character {
attack() {
console.log("The knight swings a sword!");
}
}
class Rogue extends Character {
attack() {
console.log("The rogue strikes from the shadows!");
}
}
const characters = [new Wizard(), new Knight(), new Rogue()];
characters.forEach(character => character.attack());
// Output:
// The wizard casts a fireball!
// The knight swings a sword!
// The rogue strikes from the shadows!
This example shows how method overriding lets each character type define its own attack style. When we loop over the array and call attack()
, each subclass’s overridden method runs, demonstrating polymorphism through overriding.
Overriding Built-in Methods
JavaScript allows overriding built-in object methods like toString()
. This can make objects more descriptive when logged or converted to strings.
Here’s an example with a Book
class:
class Book {
constructor(title, author) {
this.title = title;
this.author = author;
}
toString() {
return `"${this.title}" by ${this.author}`;
}
}
const myBook = new Book("The Lion King", "Arthur");
console.log(myBook.toString()); // Output: "The Lion King" by Arthur
console.log(String(myBook)); // Output: "The Lion King" by Arthur
By overriding toString()
, the Book
instances provide meaningful string representations instead of the default [object Object]
. This enhances how objects display in logs or user interfaces.
Conclusion
Method overriding in JavaScript is a key tool for creating flexible, dynamic objects that share common behavior but specialize or extend it where needed. Whether using modern ES6 classes, traditional constructor functions, or prototype-based objects, overriding lets subclasses replace or extend parent methods cleanly. This allows for rich object hierarchies, polymorphic behavior, and customization of built-in methods, all essential concepts in object-oriented programming with JavaScript.
References
If you want to learn more about method overriding and JavaScript inheritance, these resources are excellent starting points:
- MDN Web Docs: Classes
The official documentation for JavaScript classes and inheritance. - MDN Web Docs: Object prototypes
Learn how JavaScript prototype inheritance works. - MDN Web Docs:
super
Explanation and examples of usingsuper
to access parent class methods. - JavaScript Info: Prototypes, inheritance
A detailed tutorial on JavaScript’s prototype-based inheritance.