Issue #965

In JavaScript, classes are a template for creating objects. They encapsulate data with code to work on that data. ES6 introduced a class syntax to the JavaScript language to create classes in a way that’s similar to other object-oriented programming languages like Java or C#. Extending classes is also a feature of ES6, which allows you to create a new class from an existing class. This is done through the extends keyword.

Here is how you can extend a class in JavaScript:

// Define a base class
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

// Extend the base class
class Dog extends Animal {
  constructor(name, breed) {
    super(name); // Call the super class constructor and pass in the name parameter
    this.breed = breed;
  }

  speak() {
    console.log(`${this.name} barks.`);
  }
}

// Create an instance of the Dog class
let dog = new Dog('Max', 'Golden Retriever');
dog.speak(); // Output: Max barks.

In the example above:

  • Animal class is the base (or parent) class, with a constructor and a speak method.
  • Dog class is the derived (or child) class that extends the Animal class.
  • The Dog class has its own constructor. Within it, super(name) is called to access the parent class' constructor and pass any necessary arguments, in this case, the name of the animal.
  • The Dog class also overrides the speak method from the Animal class so that it reflects the behavior of a dog rather than a generic animal.

The extends keyword links the prototypes of the two classes, which is why an instance of Dog inherits properties and methods from Animal.

The use of super is mandatory in the constructor of a derived class before using this. It’s responsible for executing the constructor of the base class and setting up the prototype chain.

Classes in JavaScript can be extended from built-in objects as well, such as Array, Map, or others, allowing for custom behavior while still benefiting from built-in methods and properties.

This is the basic concept of class inheritance in JavaScript, and it enables complex hierarchies and object-oriented patterns within the language.

Extend existing class

In JavaScript, if you want to add more methods to an existing class without creating a new subclass, you can directly modify the prototype of the class. This is possible because functions, including class constructors, have a prototype property that can be manipulated. Adding a method to the class’s prototype makes that method available to all instances of the class.

Here’s an example of how to add a new method to an existing class:

// Add a new method to the existing class
Animal.prototype.dance = function() {
  console.log(`${this.name} dance!`);
}

// Now you can create a Person instance and call the new method
const animal = new Animal('Max');
animal.dance();   // Max dance

By adding dance to Animal.prototype, all instances of Animal now have access to this method. It’s also worth noting that you can add this method at any time, and it will be available to all existing and future instances of Animal.

This technique also works with built-in JavaScript objects. For example, you could add a method to Array.prototype if you wanted every array to have access to it:

Array.prototype.firstElement = function() {
  return this[0];
}

// Now every array has the firstElement method available
const myArray = [1, 2, 3];
console.log(myArray.firstElement()); // Output: 1

However, be cautious when extending the prototypes of standard JavaScript objects. Adding or changing methods on standard prototypes can have unintended side effects, especially concerning name clashes with future language features or libraries.

Function composition

Another modern alternative, if you are looking to share functionality without modifying the prototype, is to use composition. With composition, you create functions that can work on the data within your class instances:

function makeIntroductions(person) {
  console.log(`This person is named ${person.name}`);
}

// Use the function with an instance of Person
makeIntroductions(person); // Output: This person is named John

This approach favors composing behavior by passing objects to shared functions instead of relying on inheritance. It can make for more maintainable and flexible code structures, especially in complex applications.