TypeScript enhances the JavaScript class syntax by introducing type annotations, access modifiers, abstract classes, and interfaces, providing developers with a robust toolset for building scalable and type-safe applications. Class instances are created using the new keyword, and class hierarchies contribute to the construction of complex software systems in a structured and modular manner.
class Person {
// Properties with types
name: string;
age: number;
// Constructor to initialize properties
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// Methods with typed parameters and return values
greet(): string {
return `Hello, I'm ${this.name}!`;
}
}
// Creating an object instance
const john = new Person("John", 30);
// Accessing properties and methods
console.log(john.name); // logs "John"
console.log(john.greet()); // logs "Hello, I'm John!"
This example defines a Person class with name and age properties typed as string and number, respectively. The constructor initializes these properties based on arguments passed during object creation. Additionally, a greet method is defined to return a greeting message based on the name property.
Access Modifiers
TypeScript classes let you control access to members using keywords like public (accessible anywhere), private (accessible only within the class), and protected (accessible within the class and subclasses).
class Employee extends Person {
// Private salary property accessible only within the class
private _salary: number;
constructor(name: string, age: number, salary: number) {
super(name, age); // Calling parent constructor
this._salary = salary;
}
// Public get method to access salary indirectly
get salary(): number {
return this._salary;
}
// Protected raiseSalary method accessible to subclasses
protected raiseSalary(amount: number): void {
this._salary += amount;
}
}
// Accessing public members
const jane = new Employee("Jane", 25, 50000);
console.log(jane.name); // logs "Jane"
console.log(jane.salary); // logs 50000
// Cannot directly access private property
// console.log(jane._salary); // Error: Property '_salary' is private
// Using protected method in subclass
class Manager extends Employee {
public bonus(): number {
this.raiseSalary(1000); // Accessing protected method
return this.salary * 0.1;
}
}
This example demonstrates access modifiers. The _salary property in Employee is private, so it's not directly accessible. Instead, a public get method provides controlled access. raiseSalary is protected, allowing access in subclasses like Manager, which uses it to calculate a bonus.
Inheritance
Classes can inherit properties and methods from other classes using the extends keyword. This allows building specialized subclasses with additional functionality.
class Doctor extends Person {
specialty: string;
constructor(name: string, age: number, specialty: string) {
super(name, age);
this.specialty = specialty;
}
diagnose(): string {
return `Dr. ${this.name} is examining you.`;
}
}
const drMark = new Doctor("Mark", 40, "Cardiology");
console.log(drMark.greet()); // inherited from Person
console.log(drMark.diagnose()); // specific to Doctor
Here, Doctor inherits from Person and adds a specialty property and a diagnose method. This shows how inheritance promotes code reuse and specialization.
Advanced Constructors
Optional Parameters: Define parameters with default values for a more flexible initialization.
class Product {
constructor(
public name: string,
public price: number,
public brand = "Acme" // Default brand
) {}
}
const chair = new Product("Comfy Chair", 100); // Only name and price provided
console.log(chair.brand); // logs "Acme"
Rest Parameters: Capture an unlimited number of remaining arguments into an array.
class ShoppingCart {
constructor(...items: string[]) {
this.items = items;
}
addItem(newItem: string) {
this.items.push(newItem);
}
}
const cart = new ShoppingCart("Apples", "Bananas", "Cheese");
cart.addItem("Bread");
console.log(cart.items); // logs ["Apples", "Bananas", "Cheese", "Bread"]
Static Members
Define properties and methods directly attached to the class itself, not to individual instances.
class Point {
static origin = { x: 0, y: 0 };
constructor(public x: number, public y: number) {}
static distance(p1: Point, p2: Point): number {
const deltaX = p2.x - p1.x;
const deltaY = p2.y - p1.y;
return Math.sqrt(deltaX ** 2 + deltaY ** 2);
}
}
const point1 = new Point(5, 10);
console.log(Point.origin); // logs { x: 0, y: 0 }
console.log(Point.distance(point1, { x: 3, y: 4 })); // calculates distance from point1
Generics
Create classes that work with different types of data at runtime, adding flexibility and robustness.
class Queue<T> {
private items: T[] = [];
enqueue(item: T) {
this.items.push(item);
}
dequeue(): T undefined {
return this.items.shift();
}
}
const numberQueue = new Queue<number>();
numberQueue.enqueue(10);
numberQueue.enqueue(20);
const stringQueue = new Queue<string>();
stringQueue.enqueue("Hello");
stringQueue.enqueue("World");
This example shows two queues, one for numbers and one for strings, using the same Queue class with generics.
Abstract Classes
Define blueprints for subclasses but leave some methods with no implementation, forcing subclasses to provide their own.
abstract class Shape {
abstract area(): number; // Must be implemented in subclasses
perimeter(): number {
// ... implementation based on specific shape
}
}
class Circle extends Shape {
constructor(public radius: number) {}
area(): number {
return Math.PI * this.radius * this.radius;
}
perimeter(): number {
return 2 * Math.PI * this.radius; // Using parent method and adding specific calculation
}
}
const circle = new Circle(5);
console.log(circle.area()); // logs 78.53981633974483
console.log(circle.perimeter()); // logs 31.41592653589793
Decorators
Enhance classes with additional functionality at runtime.
function logClass(target: any) {
console.log(`Class decorated: ${target.name}`);
}
@logClass
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
// logs "Class decorated: User" at runtime
This example shows a decorator logging the class name when it's decorated with the @logClass annotation.
Conclusion
Classes serve as blueprints for creating objects, encapsulating both data and behavior within a single structure. They support features such as access modifiers for controlling member visibility, inheritance to facilitate code reuse, and abstract classes/interfaces for defining common blueprints.