Classes
As with other JavaScript language features, TypeScript adds type annotations and other syntax to allow you to express relationships between classes and other types.
Class Members
Here’s the most basic class - an empty one:
class Point {}
Fields
A field declaration creates a public writeable property on a class:
class Point {
x: number;
y: number;
}
const pt = new Point();
pt.x = 0;
pt.y = 0;
Fields can also have initializers; these will run automatically when the class is instantiated:
class Point {
x = 0;
y = 0;
}
const pt = new Point();
// Prints 0, 0
console.log(`${pt.x}, ${pt.y}`);
The strictPropertyInitialization
setting controls whether class fields need to be initialized in the constructor.
readonly
Fields may be prefixed with the readonly
modifier. This prevents assignments to the field outside of the constructor.
Member Visibility
You can use TypeScript to control whether certain methods or properties are visible to code outside the class.
public
- the default, a public member can be accessed anywhereprotected
- a protected member can be accessed within the class and its subclassesprivate
- a private member can only be accessed within the class
Constructors
Class constructors are very similar to functions. You can add parameters with type annotations, default values, and overloads:
class Point {
x: number;
y: number;
// Normal signature with defaults
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
There are just a few differences between class constructor signatures and function signatures:
- Constructors can’t have type parameters - these belong on the outer class declaration
- Constructors can’t have return type annotations - the class instance type is always what’s returned
Parameter Properties
TypeScript offers special syntax for turning a constructor parameter into a class property with the same name and value. These are called parameter properties and are created by prefixing a constructor argument with one of the visibility modifiers public
, private
, protected
, or readonly
. The resulting field gets those modifier(s):
class Params {
constructor(
public readonly x: number,
protected y: number,
private z: number
) {
// No body necessary
}
}
const a = new Params(1, 2, 3);
Super Calls
Just as in JavaScript, if you have a base class, you’ll need to call super();
in your constructor body before using any this
members.
Methods
A function property on a class is called a method. Methods can use all the same type annotations as functions and constructors:
class Point {
x = 10;
y = 10;
scale(n: number): void {
this.x *= n;
this.y *= n;
}
}
Other than the standard type annotations, TypeScript doesn’t add anything else new to methods.
Getters / Setters
Classes can also have accessors:
class C {
_length = 0;
get length() {
return this._length;
}
set length(value) {
this._length = value;
}
}
Note that a field-backed get/set pair with no extra logic is very rarely useful in JavaScript. It’s fine to expose public fields if you don’t need to add additional logic during the get/set operations.
TypeScript has some special inference rules for accessors:
- If
get
exists but noset
, the property is automaticallyreadonly
- If the type of the setter parameter is not specified, it is inferred from the return type of the getter
- Getters and setters must have the same Member Visibility
Static Members
Classes may have static
members. These members aren’t associated with a particular instance of the class. They can be accessed through the class constructor object itself:
class MyClass {
static x = 0;
static printX() {
console.log(MyClass.x);
}
}
console.log(MyClass.x);
MyClass.printX();
Static members can also use the same public
, protected
, and private
visibility modifiers. Static members are also inherited.
Class Heritage
Like other languages with object-oriented features, classes in JavaScript can inherit from base classes.
implements
clauses
You can use an implements
clause to check that a class satisfies a particular interface
. An error will be issued if a class fails to correctly implement it:
interface Pingable {
ping(): void;
}
class Sonar implements Pingable {
ping() {
console.log("ping!");
}
}
class Ball implements Pingable {
// Class 'Ball' incorrectly implements interface 'Pingable'.
// Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.
pong() {
console.log("pong!");
}
}
Classes may also implement multiple interfaces, e.g. class C implements A, B
.
extends
clauses
Classes may extend
from a base class. A derived class has all the properties and methods of its base class, and also define additional members:
class Animal {
move() {
console.log("Moving along!");
}
}
class Dog extends Animal {
woof(times: number) {
for (let i = 0; i < times; i++) {
console.log("woof!");
}
}
}
const d = new Dog();
// Base class method
d.move();
// Derived class method
d.woof(3);
Overriding Methods
A derived class can also override a base class field or property. You can use the super.
syntax to access base class methods. TypeScript enforces that a derived class is always a subtype of its base class.
For example, here’s a legal way to override a method:
class Base {
greet() {
console.log("Hello, world!");
}
}
class Derived extends Base {
greet(name?: string) {
if (name === undefined) {
super.greet();
} else {
console.log(`Hello, ${name.toUpperCase()}`);
}
}
}
const d = new Derived();
d.greet();
d.greet("reader");
It’s important that a derived class follow its base class contract. Remember that it’s very common (and always legal!) to refer to a derived class instance through a base class reference:
// Alias the derived instance through a base class reference
const b: Base = d;
// No problem
b.greet();
abstract
Classes and Members
An abstract method or abstract field is one that hasn’t had an implementation provided. These members must exist inside an abstract class, which cannot be directly instantiated.
The role of abstract classes is to serve as a base class for subclasses which do implement all the abstract members. When a class doesn’t have any abstract members, it is said to be concrete.
abstract class Base {
abstract getName(): string;
printName() {
console.log("Hello, " + this.getName());
}
}
// Error: Cannot create an instance of an abstract class.
const b = new Base();
We can’t instantiate Base
with new
because it’s abstract. Instead, we need to make a derived class and implement the abstract members:
class Derived extends Base {
getName() {
return "world";
}
}
const d = new Derived();
d.printName();