JavaScript classes, introduced in ECMAScript 2015 (ES6), provide a more intuitive and object-oriented way to create objects and implement inheritance. In this comprehensive guide, we'll dive deep into the basics of class syntax in JavaScript, exploring its features, benefits, and practical applications.
Understanding JavaScript Classes
Classes in JavaScript are primarily syntactical sugar over the existing prototype-based inheritance. They offer a cleaner and more familiar syntax for developers coming from class-based languages like Java or C++.
🔑 Key Point: JavaScript classes don't introduce a new object-oriented inheritance model. They're built on top of prototypes but offer a more intuitive syntax.
Let's start with a basic class declaration:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
In this example, we've defined a Person
class with a constructor and a method. Let's break it down:
- The
class
keyword is used to declare a class. - The
constructor
method is a special method for creating and initializing objects created with the class. - The
sayHello
method is defined within the class body.
To create an instance of this class, we use the new
keyword:
const john = new Person("John Doe", 30);
john.sayHello(); // Output: Hello, my name is John Doe and I'm 30 years old.
Class Expressions
Just like function expressions, we can have class expressions in JavaScript. They can be named or unnamed:
// Unnamed class expression
const Animal = class {
constructor(name) {
this.name = name;
}
};
// Named class expression
const Vehicle = class Car {
constructor(brand) {
this.brand = brand;
}
};
🔍 Note: In named class expressions, the name is only visible within the class's body.
Class Methods
Classes can have two types of methods: instance methods and static methods.
Instance Methods
Instance methods are available on instances of the class. We've already seen an example with the sayHello
method in our Person
class. Here's another example:
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
calculateArea() {
return this.width * this.height;
}
calculatePerimeter() {
return 2 * (this.width + this.height);
}
}
const rect = new Rectangle(5, 3);
console.log(rect.calculateArea()); // Output: 15
console.log(rect.calculatePerimeter()); // Output: 16
Static Methods
Static methods are called on the class itself, not on instances of the class. They're often used for utility functions related to the class.
class MathOperations {
static add(x, y) {
return x + y;
}
static multiply(x, y) {
return x * y;
}
}
console.log(MathOperations.add(5, 3)); // Output: 8
console.log(MathOperations.multiply(4, 2)); // Output: 8
🔑 Key Point: Static methods are called on the class, not on instances. They can't access instance-specific data.
Getters and Setters
Classes in JavaScript also support getters and setters, which allow you to define how a property is accessed or modified.
class Circle {
constructor(radius) {
this._radius = radius;
}
get diameter() {
return this._radius * 2;
}
set diameter(diameter) {
this._radius = diameter / 2;
}
get area() {
return Math.PI * this._radius ** 2;
}
}
const circle = new Circle(5);
console.log(circle.diameter); // Output: 10
circle.diameter = 14;
console.log(circle._radius); // Output: 7
console.log(circle.area.toFixed(2)); // Output: 153.94
In this example:
- The
diameter
getter calculates the diameter based on the radius. - The
diameter
setter updates the radius based on a new diameter value. - The
area
getter calculates the area of the circle.
🔍 Note: Getters and setters allow you to execute code on reading or writing a property, giving you more control over how object properties are accessed and modified.
Class Fields
Class fields allow you to add instance properties directly in the class body, without using the constructor. This feature is part of a more recent ECMAScript proposal and is widely supported in modern browsers and transpilers.
class Product {
name = "Unknown";
price = 0;
constructor(name, price) {
this.name = name;
this.price = price;
}
displayInfo() {
console.log(`${this.name}: $${this.price}`);
}
}
const laptop = new Product("Laptop", 999);
laptop.displayInfo(); // Output: Laptop: $999
const unknownProduct = new Product();
unknownProduct.displayInfo(); // Output: Unknown: $0
In this example, name
and price
are class fields with default values. If no arguments are passed to the constructor, these default values are used.
Private Class Features
JavaScript classes also support private fields, methods, and accessors. These are denoted by a hash (#
) prefix and are only accessible within the class body.
class BankAccount {
#balance = 0;
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#logTransaction("Deposit", amount);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
this.#logTransaction("Withdrawal", amount);
}
}
get balance() {
return this.#balance;
}
#logTransaction(type, amount) {
console.log(`${type}: $${amount}. New balance: $${this.#balance}`);
}
}
const account = new BankAccount(1000);
account.deposit(500);
account.withdraw(200);
console.log(account.balance);
// console.log(account.#balance); // This would cause an error
In this example:
#balance
is a private field.#logTransaction
is a private method.- The
balance
getter provides read-only access to the private#balance
field.
🔑 Key Point: Private class features provide encapsulation, allowing you to hide implementation details and prevent external code from directly accessing or modifying certain parts of your class.
Inheritance with Classes
One of the main advantages of classes is the clear and intuitive syntax for inheritance. You can create a child class from a parent class using the extends
keyword.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // Call the parent constructor
}
speak() {
console.log(`${this.name} barks.`);
}
fetch() {
console.log(`${this.name} fetches the ball.`);
}
}
const animal = new Animal("Generic Animal");
animal.speak(); // Output: Generic Animal makes a sound.
const dog = new Dog("Buddy");
dog.speak(); // Output: Buddy barks.
dog.fetch(); // Output: Buddy fetches the ball.
In this example:
Dog
is a subclass ofAnimal
.- The
super()
call in theDog
constructor calls the parent class constructor. - The
speak
method inDog
overrides the method fromAnimal
. - The
fetch
method is unique to theDog
class.
🔍 Note: When overriding a parent method, you can still access the parent method using super.methodName()
.
The super
Keyword
The super
keyword is used to call corresponding methods of the parent class. It can be used in two ways:
- In constructors:
super()
calls the parent constructor. - In methods:
super.methodName()
calls the parent method.
class Vehicle {
constructor(type) {
this.type = type;
}
describe() {
return `This is a ${this.type}.`;
}
}
class Car extends Vehicle {
constructor(brand) {
super('car'); // Call parent constructor
this.brand = brand;
}
describe() {
return `${super.describe()} It's a ${this.brand}.`;
}
}
const myCar = new Car('Toyota');
console.log(myCar.describe()); // Output: This is a car. It's a Toyota.
Conclusion
JavaScript classes provide a clean, intuitive syntax for creating objects and implementing inheritance. They offer a familiar structure for developers coming from class-based languages while maintaining JavaScript's prototype-based nature under the hood.
Key takeaways from this introduction to JavaScript classes include:
- Classes are templates for creating objects.
- The
constructor
method initializes new instances. - Classes can have instance methods, static methods, getters, and setters.
- Class fields allow for declaring instance properties with default values.
- Private fields and methods provide encapsulation.
- Inheritance is implemented using the
extends
keyword. - The
super
keyword is used to call parent class methods and constructors.
As you continue to work with JavaScript, you'll find that classes offer a powerful way to structure your code, especially for larger, more complex applications. They promote code reusability, maintainability, and provide a clear blueprint for object creation and behavior.
Remember, while classes offer a more traditional object-oriented syntax, JavaScript remains a flexible language that supports multiple programming paradigms. Classes are a tool in your JavaScript toolbox, and understanding when and how to use them effectively will greatly enhance your ability to write clean, efficient, and maintainable code.