Click to share! ⬇️

JavaScript classes are a relatively new feature in the language, introduced in ECMAScript 2015 (ES6). They provide a way to define blueprints for objects, allowing for more organized and modular code. Classes are essentially syntactic sugar on top of JavaScript’s existing prototype-based inheritance system. They provide a cleaner syntax and a more intuitive way to create objects and define their behaviors.

With classes, you can create templates for objects, which can then be used to create new instances of the same type of object. This can be especially useful for creating complex objects with lots of properties and methods.

In this tutorial, we’ll explore the basics of creating and using JavaScript classes. We’ll cover how to create a class, add properties and methods, use the constructor function, create objects from a class, and more. By the end of this tutorial, you’ll have a solid understanding of how to use JavaScript classes to create clean, organized, and reusable code.

Creating a JavaScript Class

To create a JavaScript class, we use the class keyword, followed by the name of the class. Here’s an example:

class Car {
  // class body
}

In this example, we’re creating a Car class. The body of the class, which is contained within curly braces {}, is where we define the properties and methods of the class.

Let’s add a property to our Car class. We can do this by defining a constructor method inside the class, like so:

class Car {
  constructor(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
  }
}

In this example, we’ve added a constructor method to our Car class, which takes three parameters: make, model, and year. The constructor method is called when we create a new instance of the class, and it sets the values of the make, model, and year properties for that instance.

Now that we’ve defined our Car class with a constructor method and some properties, we can create a new instance of the class like this:

const myCar = new Car('Toyota', 'Corolla', 2022);

In this example, we’re creating a new Car object called myCar, and passing in the values 'Toyota', 'Corolla', and 2022 as the make, model, and year parameters.

Now that we have an instance of the Car class, we can access its properties like this:

console.log(myCar.make); // output: 'Toyota'
console.log(myCar.model); // output: 'Corolla'
console.log(myCar.year); // output: 2022

In the next section, we’ll look at how to add methods to our class.

Adding Properties and Methods to a Class

In addition to adding properties to our class using the constructor method, we can also add methods to our class.

Here’s an example of how to add a method to our Car class:

class Car {
  constructor(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
  }

  start() {
    console.log(`Starting the ${this.make} ${this.model}`);
  }
}

In this example, we’ve added a start method to our Car class. This method will log a message to the console when called, indicating that the car is starting.

Now that we’ve added a method to our class, we can call it on an instance of the class like this:

const myCar = new Car('Toyota', 'Corolla', 2022);
myCar.start(); // output: 'Starting the Toyota Corolla'

In addition to adding regular methods to our class, we can also add static methods and getter/setter methods.

Static methods are methods that are attached to the class itself, rather than to instances of the class. Here’s an example of how to add a static method to our Car class:

class Car {
  constructor(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
  }

  start() {
    console.log(`Starting the ${this.make} ${this.model}`);
  }

  static info() {
    console.log('This is a car class');
  }
}

In this example, we’ve added a static info method to our Car class. This method will log a message to the console when called, indicating that this is a car class.

We can call this static method on the class itself, like this:

Car.info(); // output: 'This is a car class'

Getter and setter methods are methods that allow us to get and set the values of properties on our class. Here’s an example of how to add a getter and setter method to our Car class:

class Car {
  constructor(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
  }

  start() {
    console.log(`Starting the ${this.make} ${this.model}`);
  }

  static info() {
    console.log('This is a car class');
  }

  get age() {
    const currentYear = new Date().getFullYear();
    return currentYear - this.year;
  }

  set make(newMake) {
    this._make = newMake;
  }

  get make() {
    return this._make;
  }
}

In this example, we’ve added a get age() method, which calculates and returns the age of the car based on the current year and the year the car was made. We’ve also added a set make() method and a get make() method, which allow us to set and get the make property of the car.

Now that we’ve added getter and setter methods to our class, we can use them like this:

const myCar = new Car('Toyota', 'Corolla', 2022);
console.log(myCar.age); // output: 1
myCar.make = 'Honda';
console.log(myCar.make); // output: 'Honda'

In the next section, we’ll look at how to use the constructor function to add more complex functionality to our class.

Using the Constructor Function

The constructor function is a special method in a class that gets called when we create a new instance of the class. This method allows us to define the initial state of the object, and to set any properties or variables that we want to be available to the object.

Here’s an example of how to use the constructor function to add more complex functionality to our Car class:

class Car {
  constructor(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
    this.engineOn = false;
    this.fuelLevel = 100;
  }

  start() {
    if (this.fuelLevel > 0) {
      this.engineOn = true;
      console.log(`Starting the ${this.make} ${this.model}`);
    } else {
      console.log(`The fuel level is too low to start the ${this.make} ${this.model}`);
    }
  }

  stop() {
    this.engineOn = false;
    console.log(`Stopping the ${this.make} ${this.model}`);
  }

  static info() {
    console.log('This is a car class');
  }

  get age() {
    const currentYear = new Date().getFullYear();
    return currentYear - this.year;
  }

  set make(newMake) {
    this._make = newMake;
  }

  get make() {
    return this._make;
  }
}

In this example, we’ve added a few new properties to our Car class: engineOn and fuelLevel. We’ve also updated our start() method to check the fuel level before starting the car, and to update the engineOn property when the car is started or stopped.

Now that we’ve added more complex functionality to our class, we can create instances of our Car class and use its methods like this:

const myCar = new Car('Toyota', 'Corolla', 2022);
myCar.start(); // output: 'Starting the Toyota Corolla'
myCar.stop(); // output: 'Stopping the Toyota Corolla'
console.log(myCar.age); // output: 1
myCar.make = 'Honda';
console.log(myCar.make); // output: 'Honda'

In the next section, we’ll look at how to extend a class to create a subclass.

Creating Objects from a Class

To create an object from a class, we use the new keyword followed by the name of the class and any arguments required by the class’s constructor function.

Here’s an example of how to create an object from our Car class:

const myCar = new Car('Toyota', 'Corolla', 2022);

In this example, we’ve created an instance of the Car class called myCar and passed in the arguments 'Toyota', 'Corolla', and 2022 to the class’s constructor function.

Once we have an instance of our Car class, we can use its methods and properties like this:

myCar.start();
myCar.stop();
console.log(myCar.age);

These method and property calls will only affect the myCar instance, and not any other instances of the Car class that we create.

We can also create multiple instances of a class like this:

const car1 = new Car('Toyota', 'Corolla', 2022);
const car2 = new Car('Honda', 'Civic', 2021);

In this example, we’ve created two instances of the Car class called car1 and car2, each with their own properties and methods.

In the next section, we’ll look at how to use static methods and properties in a class.

Inheritance and Extending Classes

Inheritance is a key concept in object-oriented programming, and it allows us to create new classes that are based on existing classes. In JavaScript, we can use the extends keyword to create a subclass that inherits properties and methods from a superclass.

Here’s an example of how to create a Truck class that extends the Car class:

class Truck extends Car {
  constructor(make, model, year, payloadCapacity) {
    super(make, model, year);
    this.payloadCapacity = payloadCapacity;
  }

  loadCargo() {
    console.log(`Loading cargo. Payload capacity: ${this.payloadCapacity} pounds.`);
  }
}

In this example, we’ve created a new Truck class that extends the Car class. We’ve passed in the make, model, and year arguments to the super method, which calls the constructor of the superclass. We’ve also added a new payloadCapacity property and a loadCargo method to the Truck class.

Now we can create an instance of the Truck class and use its methods and properties like this:

const myTruck = new Truck('Ford', 'F-150', 2022, 2000);
myTruck.start(); // output: 'Starting the Ford F-150'
myTruck.loadCargo(); // output: 'Loading cargo. Payload capacity: 2000 pounds.'

In this example, we’ve created a new instance of the Truck class called myTruck, passed in the required arguments, and called the start and loadCargo methods.

We can also access the properties and methods of the superclass from the subclass like this:

console.log(myTruck.age);
myTruck.make = 'Chevrolet';
console.log(myTruck.make);

In this example, we’ve called the age property and the make property of the superclass Car, and we’ve updated the make property of the myTruck instance.

Overriding Methods in Child Classes

When we create a subclass that extends a superclass, we can override methods of the superclass in the subclass. This allows us to change the behavior of the method for the subclass.

Here’s an example of how to override the start method of the Car class in the Truck subclass:

class Truck extends Car {
  constructor(make, model, year, payloadCapacity) {
    super(make, model, year);
    this.payloadCapacity = payloadCapacity;
  }

  start() {
    console.log(`Starting the ${this.make} ${this.model} truck. Please wait...`);
    super.start();
  }

  loadCargo() {
    console.log(`Loading cargo. Payload capacity: ${this.payloadCapacity} pounds.`);
  }
}

In this example, we’ve overridden the start method of the Car class in the Truck subclass. We’ve added some additional console output to the method, and we’ve also called the start method of the superclass Car using the super keyword.

Now when we create an instance of the Truck class and call the start method, we’ll see the overridden behavior:

const myTruck = new Truck('Ford', 'F-150', 2022, 2000);
myTruck.start(); // output: 'Starting the Ford F-150 truck. Please wait...' followed by 'Starting the Ford F-150'

In this example, we’ve called the start method of the myTruck instance, and we’ve seen the overridden behavior followed by the behavior of the superclass method.

We can also override other methods of the superclass in the subclass, and we can call the superclass method using the super keyword. This allows us to reuse the behavior of the superclass while also adding new functionality to the subclass.

Using Super to Call Parent Methods

In addition to overriding methods of the superclass in the subclass, we can also call the methods of the superclass from the subclass using the super keyword. This allows us to reuse the behavior of the superclass while also adding new functionality to the subclass.

Here’s an example of how to use the super keyword to call the start method of the Car superclass in the Truck subclass:

class Truck extends Car {
  constructor(make, model, year, payloadCapacity) {
    super(make, model, year);
    this.payloadCapacity = payloadCapacity;
  }

  start() {
    console.log(`Starting the ${this.make} ${this.model} truck. Please wait...`);
    super.start();
  }

  loadCargo() {
    console.log(`Loading cargo. Payload capacity: ${this.payloadCapacity} pounds.`);
  }
}

In this example, we’ve added the start method to the Truck subclass and called the start method of the superclass Car using the super keyword. This allows us to reuse the behavior of the start method of the Car superclass while also adding new functionality to the start method of the Truck subclass.

We can also use the super keyword to call the constructor of the superclass from the subclass:

class Truck extends Car {
  constructor(make, model, year, payloadCapacity) {
    super(make, model, year);
    this.payloadCapacity = payloadCapacity;
  }
}

In this example, we’ve called the constructor of the superclass Car using the super keyword and passed in the required arguments. This allows us to reuse the behavior of the constructor of the Car superclass while also adding new functionality to the constructor of the Truck subclass.

Static Properties and Methods

Static properties and methods are attached to the class itself rather than to the instances of the class. This means that you can access static properties and methods without creating an instance of the class.

To define a static property or method, you can use the static keyword. Here’s an example of how to define a static property and method in a class:

class Car {
  constructor(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
  }

  start() {
    console.log(`Starting the ${this.make} ${this.model}`);
  }

  static getInfo() {
    console.log('This is a car class.');
  }
}

In this example, we’ve defined a static method called getInfo using the static keyword. We can call this method directly on the Car class, without creating an instance of the class:

Car.getInfo(); // output: 'This is a car class.'

We can also define a static property using the static keyword:

class Car {
  static category = 'Vehicle';

  constructor(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
  }

  start() {
    console.log(`Starting the ${this.make} ${this.model}`);
  }
}

In this example, we’ve defined a static property called category using the static keyword. We can access this property directly on the Car class, without creating an instance of the class:

console.log(Car.category); // output: 'Vehicle'

Static properties and methods can be useful for providing functionality that is related to the class as a whole, rather than to specific instances of the class.

Getters and Setters

Getters and setters are methods that allow you to get and set the values of an object’s properties. They can be used to control access to the object’s properties and to ensure that the properties are used correctly.

To define a getter or setter, you can use the get and set keywords followed by the name of the property you want to get or set. Here’s an example of how to define a getter and setter in a class:

class Car {
  constructor(make, model, year) {
    this._make = make;
    this._model = model;
    this._year = year;
  }

  get make() {
    return this._make;
  }

  set make(value) {
    if (value.length < 3) {
      console.log('Make must be at least 3 characters long.');
      return;
    }
    this._make = value;
  }

  get model() {
    return this._model;
  }

  set model(value) {
    if (value.length < 3) {
      console.log('Model must be at least 3 characters long.');
      return;
    }
    this._model = value;
  }

  get year() {
    return this._year;
  }

  set year(value) {
    if (value < 1900) {
      console.log('Year must be after 1900.');
      return;
    }
    this._year = value;
  }

  start() {
    console.log(`Starting the ${this._make} ${this._model}`);
  }
}

In this example, we’ve defined getters and setters for the make, model, and year properties of the Car class. The getters and setters use the _make, _model, and _year properties to get and set the values of the properties. We’ve also added validation to the setters to ensure that the values are set correctly.

Here’s an example of how to use the getters and setters:

const myCar = new Car('Toyota', 'Corolla', 2020);

console.log(myCar.make); // output: 'Toyota'
myCar.make = 'To'; // output: 'Make must be at least 3 characters long.'
myCar.make = 'Honda'; // set make to 'Honda'
console.log(myCar.make); // output: 'Honda'

console.log(myCar.year); // output: 2020
myCar.year = 1800; // output: 'Year must be after 1900.'
myCar.year = 2022; // set year to 2022
console.log(myCar.year); // output: 2022

In this example, we’ve created an instance of the Car class and used the getters and setters to get and set the values of the make, model, and year properties. We’ve also tested the validation of the setters to ensure that the values are set correctly.

Class Syntax vs. Prototype-Based Syntax

Before the introduction of classes in ES6, JavaScript used a prototype-based syntax to define objects and their behaviors. Here’s an example of how to define an object using the prototype-based syntax:

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

Car.prototype.start = function() {
  console.log(`Starting the ${this.make} ${this.model}`);
};

In this example, we’ve defined a Car function that takes make, model, and year as parameters and sets them as properties of the object. We’ve also defined a start function on the Car function’s prototype, which logs a message to the console.

Here’s how we’d create an instance of the Car object using the prototype-based syntax:

const myCar = new Car('Toyota', 'Corolla', 2020);
myCar.start(); // output: 'Starting the Toyota Corolla'

With the introduction of classes in ES6, we can define objects and their behaviors in a more familiar class-based syntax. Here’s an example of how to define the same Car class using the class syntax:

class Car {
  constructor(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
  }

  start() {
    console.log(`Starting the ${this.make} ${this.model}`);
  }
}

In this example, we’ve defined the Car class using the class keyword, and defined the make, model, and year properties using the constructor method. We’ve also defined the start method using the class syntax.

Here’s how we’d create an instance of the Car object using the class syntax:

const myCar = new Car('Toyota', 'Corolla', 2020);
myCar.start(); // output: 'Starting the Toyota Corolla'

Both the prototype-based syntax and the class syntax accomplish the same thing – defining objects and their behaviors in JavaScript. However, the class syntax is more familiar to developers coming from other programming languages, and it provides some additional features like inheritance and static properties/methods. That being said, the prototype-based syntax is still widely used and has some advantages, such as greater flexibility and dynamic behavior.

Best Practices for Using JavaScript Classes

JavaScript classes can be a powerful tool for organizing and structuring your code, but like any tool, they can also be misused. Here are some best practices for using JavaScript classes effectively:

  1. Use classes to organize related functionality: Classes should be used to group together related properties and methods, creating a coherent and self-contained unit of functionality.
  2. Keep classes small and focused: Classes should have a clear and specific purpose, and they should not try to do too much. If a class is getting too large or complex, consider breaking it up into smaller classes or helper functions.
  3. Use constructor arguments to define properties: When defining properties on a class, use the constructor to set their initial values. This makes it clear what properties the class has, and ensures that they are always initialized when a new instance is created.
  4. Use methods to define behavior: Methods should define the behavior of a class, and they should operate on the class’s properties. Avoid defining methods that operate on external state or that have side effects.
  5. Use static methods sparingly: Static methods should be used sparingly, and only for functionality that is truly independent of the state of the class. If a method needs to access the state of the class, it should be an instance method.
  6. Use inheritance carefully: Inheritance can be a useful tool for creating related classes, but it can also lead to tight coupling between classes and make your code harder to understand and maintain. Use inheritance only when it makes sense, and keep your inheritance hierarchies shallow and simple.
  7. Prefer composition over inheritance: Composition is often a better way to achieve code reuse than inheritance. By creating classes that contain instances of other classes, you can create complex behaviors that are composable and easy to understand.
  8. Use getters and setters for computed properties: Getters and setters can be used to define computed properties on a class, which are properties that are calculated based on the state of the class. This can make it easier to reason about the behavior of the class and ensure that its properties are always in a valid state.

By following these best practices, you can create JavaScript classes that are well-organized, maintainable, and easy to understand.

Click to share! ⬇️