JavaScript Classes
JavaScript classes, introduced in ECMAScript 2015 (ES6), provide a cleaner and more elegant syntax for creating objects and dealing with inheritance. While JavaScript remains prototype-based, classes offer a more familiar syntax for developers coming from class-based languages.
Class Basics
A JavaScript class is a type of function, but instead of using the function keyword, we use the class keyword and the properties are assigned inside a constructor() method.
// Class declaration
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// Method
greet() {
return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
}
}
// Creating an instance
const john = new Person('John', 30);
console.log(john.greet()); // "Hello, my name is John and I am 30 years old."
Note: Class declarations are not hoisted. You need to declare a class before you can use it.
Class Expressions
Similar to function expressions, classes can also be defined using expressions:
// Unnamed class expression
const Person = class {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
}
};
// Named class expression
const Employee = class EmployeeClass {
constructor(name, position) {
this.name = name;
this.position = position;
}
};
Class Methods
Classes can contain several types of methods:
Constructor Method
The constructor method is a special method for creating and initializing objects created with a class. There can only be one constructor method in a class.
Instance Methods
Instance methods are functions that are available on instances of the class:
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
}
const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
console.log(calc.subtract(10, 4)); // 6
Static Methods
Static methods are called on the class itself, not on instances of the class:
class MathUtils {
static PI = 3.14159;
static square(x) {
return x * x;
}
static cube(x) {
return x * x * x;
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.square(4)); // 16
console.log(MathUtils.cube(3)); // 27
// This would throw an error:
// const math = new MathUtils();
// console.log(math.square(4));
Getter and Setter Methods
Classes also support getter and setter methods, which allow you to control access to class properties:
class Temperature {
constructor(celsius) {
this._celsius = celsius;
}
get celsius() {
return this._celsius;
}
set celsius(value) {
if (value < -273.15) {
throw new Error('Temperature below absolute zero is not possible');
}
this._celsius = value;
}
get fahrenheit() {
return this._celsius * 9/5 + 32;
}
set fahrenheit(value) {
this.celsius = (value - 32) * 5/9;
}
}
const temp = new Temperature(25);
console.log(temp.celsius); // 25
console.log(temp.fahrenheit); // 77
temp.celsius = 30;
console.log(temp.fahrenheit); // 86
temp.fahrenheit = 68;
console.log(temp.celsius); // 20
Class Inheritance
Classes can inherit from other classes using the extends keyword:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a noise.`;
}
}
class Dog extends Animal {
constructor(name, breed) {
// Call the parent constructor
super(name);
this.breed = breed;
}
// Override the parent method
speak() {
return `${this.name} barks!`;
}
// Add a new method
getBreed() {
return `${this.name} is a ${this.breed}.`;
}
}
const dog = new Dog('Rex', 'German Shepherd');
console.log(dog.speak()); // "Rex barks!"
console.log(dog.getBreed()); // "Rex is a German Shepherd."
Note: The super
keyword is used to call the constructor of the parent class. If you use the this
keyword in a constructor, you must call super()
first.
Private Class Features
Modern JavaScript (ES2022) supports private class fields and methods using the # prefix:
0) {
this.#deposit(initialBalance);
}
}
// Private method
#deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}
// Public methods
deposit(amount) {
return this.#deposit(amount);
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
return true;
}
return false;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount('John Doe', 1000);
console.log(account.getBalance()); // 1000
account.deposit(500);
console.log(account.getBalance()); // 1500
account.withdraw(200);
console.log(account.getBalance()); // 1300
// This would throw an error:
// console.log(account.#balance);
// account.#deposit(100);]]>
Warning: Private class features are a relatively new addition to JavaScript. They may not be supported in older browsers without transpilation.
Implementing Interfaces
JavaScript doesn't have built-in interfaces like TypeScript or other strongly-typed languages. However, you can implement interface-like patterns:
// Define an "interface" as a set of methods a class should implement
const ShapeInterface = {
calculateArea() {},
calculatePerimeter() {}
};
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
calculateArea() {
return this.width * this.height;
}
calculatePerimeter() {
return 2 * (this.width + this.height);
}
}
class Circle {
constructor(radius) {
this.radius = radius;
}
calculateArea() {
return Math.PI * this.radius * this.radius;
}
calculatePerimeter() {
return 2 * Math.PI * this.radius;
}
}
// Function that works with any "shape" that implements the interface
function printShapeInfo(shape) {
console.log(`Area: ${shape.calculateArea()}`);
console.log(`Perimeter: ${shape.calculatePerimeter()}`);
}
const rect = new Rectangle(5, 10);
const circle = new Circle(7);
printShapeInfo(rect);
printShapeInfo(circle);
Mixins
Mixins are a way to add methods to classes without inheritance:
// Mixin
const SpeakerMixin = {
speak(phrase) {
console.log(`${this.name} says: ${phrase}`);
}
};
const SwimmerMixin = {
swim() {
console.log(`${this.name} is swimming.`);
}
};
// Base class
class Person {
constructor(name) {
this.name = name;
}
}
// Apply mixins
Object.assign(Person.prototype, SpeakerMixin);
Object.assign(Person.prototype, SwimmerMixin);
const person = new Person('John');
person.speak('Hello!'); // "John says: Hello!"
person.swim(); // "John is swimming."
Interactive Example
Try out this interactive example to see JavaScript classes in action:
Best Practices
- Keep classes focused: Each class should have a single responsibility.
- Use private fields: Encapsulate internal state using private fields when possible.
- Prefer composition over inheritance: Deep inheritance hierarchies can be difficult to maintain.
- Document your classes: Use JSDoc comments to document the purpose and usage of your classes.
- Be consistent: Follow a consistent naming convention for your classes and methods.
Next Steps
Now that you understand JavaScript classes, you might want to explore:
- Design Patterns in JavaScript
- TypeScript Classes and Interfaces
- Web Components
- Object-Oriented Programming Principles