Modern JavaScript development relies heavily on object-oriented programming patterns, and the extends keyword is the foundation of class inheritance in ES6+. This powerful feature allows developers to create hierarchies of related classes, promoting code reuse and cleaner architecture. Whether you're building a complex React application, a Node.js API, or any JavaScript project, understanding extends is essential for writing maintainable, organized code that scales effectively. Our web development services team specializes in building scalable JavaScript applications using modern best practices.
What is the extends Keyword?
The extends keyword is used in class declarations or class expressions to create a class that is a child of another class. This fundamental feature of JavaScript's class syntax enables inheritance, allowing child classes to automatically receive properties and methods from their parent classes without having to rewrite code.
In practical terms, extends creates what developers often call an "is-a" relationship between classes. For example, if you have a Vehicle class and create an ElectricCar class that extends it, you're expressing that an electric car "is-a" vehicle. This relationship means the electric car automatically inherits all the characteristics of a vehicle while adding its own specialized features.
How extends Works Under the Hood
When you declare a class that uses extends, JavaScript performs several behind-the-scenes operations to establish the inheritance relationship:
- Sets the prototype of the child class constructor to be the parent class constructor
- Sets the prototype of the child class's prototype object to be an instance of the parent class's prototype object
- These two connections form the prototype chain that enables property and method inheritance
Syntax and Basic Usage
Class Declaration with extends
The most common use of extends is in class declarations:
class ElectricCar extends Vehicle {
constructor(make, model, batteryCapacity) {
super(make, model); // Call parent constructor first
this.batteryCapacity = batteryCapacity;
}
charge() {
return `${this.brand} ${this.model} is charging.`;
}
}
const tesla = new ElectricCar("Tesla", "Model 3", 75);
console.log(tesla.drive()); // Inherited from Vehicle
console.log(tesla.charge()); // Defined in ElectricCar
When creating an instance, the process follows a specific order. First, the child class's constructor calls super(), which invokes the parent class's constructor. Only after the parent constructor completes can the child constructor access this and perform additional initialization.
Class Expressions with extends
extends also works with class expressions for dynamic class definitions:
const createVehicle = (type) => {
return class extends BaseVehicle {
constructor(props) {
super(props);
this.vehicleType = type;
}
};
};
const Car = createVehicle("car");
1// Basic extends example2class Animal {3 constructor(name) {4 this.name = name;5 }6 7 speak() {8 return `${this.name} makes a sound.`;9 }10}11 12class Dog extends Animal {13 speak() {14 return `${this.name} barks!`;15 }16}17 18const dog = new Dog("Rex");19console.log(dog.speak());20// Rex barks!21 22// Inherited property23console.log(dog.name); // RexInheriting Properties and Methods
Instance Properties and Methods
When a class inherits using extends, all instance properties and methods defined on the parent class become available to instances of the child class. This inheritance happens through the prototype chain, meaning methods exist only once in memory, shared across all instances.
Key inheritance behaviors:
- Inherited methods behave exactly like methods defined directly on the child class
thisrefers to the instance itself when methods are called- Property shadowing: if both classes define a property, the child's takes precedence
- Call parent's version using
super.methodName()
Static Properties and Methods
The extends keyword also handles static members correctly:
class Vehicle {
static count = 0;
static getDescription() {
return "A means of transportation";
}
}
class Car extends Vehicle {}
console.log(Car.count); // Inherited static property
console.log(Car.getDescription()); // Inherited static method
The super Keyword
Using super() in Constructors
The super() keyword is essential for proper initialization:
- Must be called before accessing
thisin the constructor - Invokes the parent class's constructor
- Can include parameters passed to the parent constructor
- JavaScript throws ReferenceError if called incorrectly
class Vehicle {
constructor(make, model) {
this.make = make;
this.model = model;
}
}
class ElectricCar extends Vehicle {
constructor(make, model, batteryCapacity) {
super(make, model); // Required before this
this.batteryCapacity = batteryCapacity;
}
}
Using super for Methods
Call parent class methods from within the child class:
class LoggingService {
log(message) {
console.log(`[LOG]: ${message}`);
}
}
class FilteredLoggingService extends LoggingService {
log(message) {
const filtered = message.replace(/password/gi, "***");
super.log(filtered); // Call parent's log
}
}
1// Super in method overriding2class Shape {3 draw() {4 console.log("Drawing a shape");5 }6}7 8class Circle extends Shape {9 draw() {10 // Call parent's draw first11 super.draw();12 console.log("Drawing a circle");13 }14}15 16const circle = new Circle();17// Output:18// Drawing a shape19// Drawing a circle20 21// Constructor with super22class Parent {23 constructor(value) {24 this.parentValue = value;25 }26}27 28class Child extends Parent {29 constructor(value, childValue) {30 super(value); // Pass to parent31 this.childValue = childValue;32 }33}Best Practices
Composition vs Inheritance
Modern JavaScript development often favors composition over inheritance. Understanding when to use each approach is crucial for building maintainable applications. While inheritance creates clear hierarchies, composition offers more flexibility through object composition.
Use inheritance (extends) when:
- You have a clear "is-a" relationship
- The hierarchy is stable and unlikely to change
- Child classes share significant behavior with parents
- You want to leverage polymorphism
Use composition when:
- You need runtime flexibility in behavior
- Relationships are "has-a" or "uses-a"
- You want to avoid tight coupling
- Multiple independent features need to be combined
Avoiding Common Pitfalls
- Avoid deep inheritance hierarchies - Keep to 1-2 levels when possible
- Don't rely on parent implementation details - Use documented extension points
- Be cautious extending built-in objects - May cause unexpected behavior
- Document which methods are intended for overriding
// Good: Shallow hierarchy
class BaseModel {
validate() { /* ... */ }
save() { /* ... */ }
}
class UserModel extends BaseModel {
validate() {
super.validate();
// Additional validation
}
}
// Better for complex scenarios: Composition
class WithLogging {
log(message) { /* ... */ }
}
class UserService {
constructor() {
this.logger = new WithLogging();
}
}
Performance Considerations
Prototype Chain Efficiency
One advantage of extends is memory efficiency. Methods exist only once in memory, shared across all instances through the prototype chain. Modern JavaScript engines have extensive optimizations for this pattern.
Performance characteristics:
- Method calls through inheritance are essentially free in modern engines
- Property lookups through the prototype chain have minimal overhead
- Classes and prototypes interoperate seamlessly
When to Optimize
Performance optimization should be driven by measurement:
- Use profiling tools to identify actual bottlenecks
- For most applications, inheritance performance is negligible
- If issues exist, optimize hot paths rather than abandoning inheritance
- Consider caching frequently accessed properties
"Premature optimization is the root of all evil." - Use
extendsfor design clarity first, optimize only when measured bottlenecks exist.
Why extends is efficient in modern JavaScript
Shared Methods
One copy of each method in memory, shared across all instances
Engine Optimizations
Modern JavaScript engines have years of optimization work for prototype chains
No Syntax Overhead
Class syntax adds no meaningful overhead compared to manual prototypes
Garbage Collection
Shared prototypes reduce memory pressure and GC cycles
Common Patterns and Use Cases
Model-View-Controller (MVC) Patterns
The extends keyword is commonly used in MVC architectures:
// Base model with common functionality
class Model {
constructor(data) {
this.data = data;
}
validate() {
return Object.keys(this.validationRules).every(
rule => this.data[rule.field] !== undefined
);
}
save() {
if (this.validate()) {
return this.persist();
}
throw new Error("Validation failed");
}
}
// Specific model extends base
class UserModel extends Model {
constructor(data) {
super(data);
this.validationRules = {
email: { field: "email" },
name: { field: "name" }
};
}
}
Plugin and Extension Systems
// Base plugin class
class Plugin {
constructor(app) {
this.app = app;
}
install() {}
uninstall() {}
}
// User can create custom plugins
class CustomPlugin extends Plugin {
install() {
this.app.on("request", this.handleRequest);
}
handleRequest(req) {
// Custom logic
}
}
Frequently Asked Questions
Can I extend multiple classes in JavaScript?
No, JavaScript only supports single inheritance. A class can extend only one parent class. However, you can achieve multiple inheritance-like patterns using mixins or composition.
When should I use super()?
Use super() in the constructor before accessing this. It must be called to invoke the parent constructor. Use super.methodName() to call parent methods when overriding them.
Does extends work with class expressions?
Yes, extends works with both class declarations and class expressions. This allows for dynamic class creation and enables patterns like mixins.
How is extends different from prototype inheritance?
extends is syntactic sugar over JavaScript's prototype system. It creates the same prototype chain that was previously done manually with Object.create() and prototype assignment.
Conclusion
The extends keyword is a cornerstone of object-oriented programming in modern JavaScript, enabling clean, maintainable code through inheritance. By allowing child classes to automatically receive properties and methods from parent classes, extends promotes code reuse and establishes clear relationships between related types.
While composition has gained popularity, extends remains the right choice for stable taxonomies of related types. The key is understanding when each approach serves your design goals best, and applying extends judiciously to create code that's both well-organized and flexible.
As JavaScript continues to evolve, the class syntax with extends remains essential for any JavaScript developer to understand. Our web development services team can help you implement clean, maintainable code patterns including proper class inheritance in your projects.