OOP#

TypeScript classes are JavaScript classes plus access modifiers (public, protected, private), parameter properties, abstract classes, implements clauses, and generic class declarations. The runtime semantics are JavaScript’s; the type-only features are erased at compile time.

For the runtime mechanics, see OOP.

Class declarations#

The familiar JavaScript class with type annotations on fields, parameters, and returns.

class Point {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  distance(other: Point): number {
    const dx = this.x - other.x;
    const dy = this.y - other.y;
    return Math.hypot(dx, dy);
  }
}

const p = new Point(0, 0);
const q = new Point(3, 4);
p.distance(q);                            // 5

Parameter properties#

A constructor parameter annotated with public, private, protected, or readonly is automatically declared and assigned as a field.

class Point {
  constructor(public x: number, public y: number) {}
}

const p = new Point(1, 2);
p.x;                                       // 1

The two forms (explicit fields + assignment vs parameter properties) compile to the same JavaScript. The parameter form is shorter; the explicit form is clearer when fields need initialisation or transformation.

Access modifiers#

Modifier

Effect

public

default. Accessible from anywhere.

protected

accessible inside the class and its subclasses.

private

compile-time only. Type-erased; nothing prevents runtime access through string keys.

#name (private field)

runtime private; cannot be accessed outside the class at all. Use this when actual privacy matters.

class Cache {
  #data = new Map<string, unknown>();     // runtime private
  private hits = 0;                       // compile-time private

  get(k: string) {
    const v = this.#data.get(k);
    if (v !== undefined) this.hits++;
    return v;
  }
}

For greenfield code, pick #name for actual privacy and private only when interop with sibling classes (via reflection or tests) is needed.

readonly fields#

readonly allows assignment only in the constructor.

class Config {
  readonly host: string;
  readonly port: number;

  constructor(host: string, port: number) {
    this.host = host;
    this.port = port;
  }
}

Inheritance#

extends for single inheritance; super(...) for the parent constructor; super.method(...) for the parent method.

class Animal {
  constructor(public name: string) {}
  speak(): void { console.log(`${this.name} makes a sound`); }
}

class Dog extends Animal {
  speak(): void {
    super.speak();
    console.log(`${this.name} barks`);
  }
}

abstract#

abstract classes cannot be instantiated; abstract methods have no body and must be implemented by subclasses.

abstract class Shape {
  abstract area(): number;
  describe(): string { return `area: ${this.area()}`; }
}

class Circle extends Shape {
  constructor(public r: number) { super(); }
  area(): number { return Math.PI * this.r ** 2; }
}

implements#

An interface describes a structure; a class can declare it implements that interface and the compiler verifies the class conforms.

interface Listener { onMessage(msg: string): void; }

class Logger implements Listener {
  onMessage(msg: string): void { console.log(msg); }
}

Use implements to assert the contract at the class declaration site; without it, the class still conforms structurally but a typo silently breaks the contract.

Generics on classes#

A class can carry type parameters that thread through its methods.

class Container<T> {
  constructor(public value: T) {}
  map<U>(fn: (x: T) => U): Container<U> {
    return new Container(fn(this.value));
  }
}

const c = new Container(42);              // Container<number>
const s = c.map(n => n.toString());       // Container<string>

Static members#

static puts a field or method on the class itself, not on instances.

class Server {
  static readonly DEFAULT_PORT = 8080;
  static create(): Server { return new Server(); }
}

Server.DEFAULT_PORT;                       // 8080
Server.create();                           // Server

Accessors#

get and set are runtime accessors; types describe the read and write structures (can differ).

class Temp {
  constructor(private c: number) {}
  get fahrenheit(): number { return this.c * 9/5 + 32; }
  set fahrenheit(f: number) { this.c = (f - 32) * 5/9; }
}

Class as a type#

A class name in a type position is the instance type. The constructor type is typeof Class.

class Server { /* … */ }

function factory(Ctor: typeof Server): Server {
  return new Ctor();
}

type ServerInstance = InstanceType<typeof Server>;     // Server

this types#

Methods that return this enable fluent / builder APIs and preserve the subclass type.

class Query {
  where(cond: string): this { /* … */ return this; }
  order(by: string): this   { /* … */ return this; }
}

class UserQuery extends Query {
  active(): this { /* … */ return this; }
}

new UserQuery().where("x").order("y").active();  // UserQuery, not Query

References#