ES6 Classes: Syntax and Features
ES6 classes provide a cleaner syntax for creating objects and dealing with inheritance compared to prototype-based approaches.
Class Basics
Class Declaration
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
const person = new Person("Alice", 30);
console.log(person.greet()); // "Hello, I'm Alice"
Class Expression
// Anonymous class
const Person = class {
constructor(name) {
this.name = name;
}
};
// Named class expression
const Animal = class AnimalClass {
constructor(name) {
this.name = name;
}
};
Constructor
The constructor method runs when creating a new instance:
class User {
constructor(username, email) {
this.username = username;
this.email = email;
this.createdAt = new Date();
}
}
const user = new User("alice", "[email protected]");
console.log(user.createdAt); // Current date
Methods
Instance Methods
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
multiply(a, b) {
return a * b;
}
}
const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
Static Methods
Static methods belong to the class, not instances:
class MathUtils {
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
}
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.multiply(5, 3)); // 15
const utils = new MathUtils();
console.log(utils.add(5, 3)); // TypeError
Getters and Setters
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
set fullName(name) {
[this.firstName, this.lastName] = name.split(" ");
}
}
const person = new Person("John", "Doe");
console.log(person.fullName); // "John Doe"
person.fullName = "Jane Smith";
console.log(person.firstName); // "Jane"
console.log(person.lastName); // "Smith"
Inheritance
Extends Keyword
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
return `${this.name} barks`;
}
}
const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.speak()); // "Buddy barks"
Super Keyword
Call parent class methods:
class Vehicle {
constructor(brand) {
this.brand = brand;
}
start() {
return `${this.brand} started`;
}
}
class Car extends Vehicle {
constructor(brand, model) {
super(brand);
this.model = model;
}
start() {
const parentStart = super.start();
return `${parentStart} - ${this.model} is ready`;
}
}
const car = new Car("Toyota", "Camry");
console.log(car.start()); // "Toyota started - Camry is ready"
Private Fields
class BankAccount {
#balance = 0; // Private field
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.#balance); // SyntaxError - private field
Static Properties
class Counter {
static count = 0;
constructor() {
Counter.count++;
}
static getCount() {
return Counter.count;
}
}
new Counter();
new Counter();
new Counter();
console.log(Counter.getCount()); // 3
Practical Examples
User Management System
class User {
constructor(id, username, email) {
this.id = id;
this.username = username;
this.email = email;
this.createdAt = new Date();
}
getInfo() {
return `${this.username} (${this.email})`;
}
}
class Admin extends User {
constructor(id, username, email, permissions = []) {
super(id, username, email);
this.permissions = permissions;
}
hasPermission(permission) {
return this.permissions.includes(permission);
}
addPermission(permission) {
if (!this.permissions.includes(permission)) {
this.permissions.push(permission);
}
}
}
const admin = new Admin(1, "admin", "[email protected]", ["read", "write"]);
console.log(admin.getInfo()); // "admin ([email protected])"
console.log(admin.hasPermission("delete")); // false
admin.addPermission("delete");
console.log(admin.hasPermission("delete")); // true
Shape Hierarchy
class Shape {
constructor(color) {
this.color = color;
}
getArea() {
throw new Error("getArea() must be implemented");
}
describe() {
return `A ${this.color} shape`;
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
}
getArea() {
return Math.PI * this.radius ** 2;
}
describe() {
return `${super.describe()} with radius ${this.radius}`;
}
}
class Rectangle extends Shape {
constructor(color, width, height) {
super(color);
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
const circle = new Circle("red", 5);
console.log(circle.getArea()); // 78.54...
console.log(circle.describe()); // "A red shape with radius 5"
const rect = new Rectangle("blue", 10, 20);
console.log(rect.getArea()); // 200
Database Model
class Model {
static #instances = [];
constructor(data) {
this.id = Date.now();
this.createdAt = new Date();
Object.assign(this, data);
Model.#instances.push(this);
}
static findAll() {
return Model.#instances;
}
static findById(id) {
return Model.#instances.find(instance => instance.id === id);
}
save() {
// Simulate saving
return true;
}
delete() {
const index = Model.#instances.indexOf(this);
if (index > -1) {
Model.#instances.splice(index, 1);
}
}
}
class User extends Model {
constructor(username, email) {
super({ username, email });
}
}
const user1 = new User("alice", "[email protected]");
const user2 = new User("bob", "[email protected]");
console.log(Model.findAll().length); // 2
console.log(Model.findById(user1.id).username); // "alice"
Class vs Prototype
Prototype Approach
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, ${this.name}`;
};
const person = new Person("Alice");
Class Approach
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}`;
}
}
const person = new Person("Alice");
Both are equivalent, but classes are cleaner and more intuitive.
Best Practices
Use Meaningful Names
// Good
class UserRepository {
findById(id) { }
}
// Avoid
class UR {
fbi(id) { }
}
Keep Classes Focused
// Good - single responsibility
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
// Avoid - too many responsibilities
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
sendEmail() { }
validateEmail() { }
saveToDatabase() { }
}
Use Private Fields for Encapsulation
// Good - encapsulation
class BankAccount {
#balance = 0;
deposit(amount) {
this.#balance += amount;
}
}
// Avoid - no encapsulation
class BankAccount {
constructor() {
this.balance = 0;
}
}
Summary
- Class: blueprint for creating objects
- Constructor: initializes new instances
- Methods: functions in a class
- Static: methods/properties on class itself
- Getters/Setters: special methods for properties
- Inheritance: extend classes with
extends - Super: call parent class methods
- Private fields: encapsulation with
# - Best practice: use classes for OOP
Comments