1.6 Classes
JavaScript practice has two very common design patterns: the module pattern and the class pattern. Roughly speaking, the module pattern uses closures to hide names and to encapsulate private data, while the class pattern uses prototype chains to implement many variations on object-oriented inheritance mechanisms. Libraries such as ‘prototype.js’ are typical of this practice. TypeScript’s namespaces are a formalization of the module pattern. (The term “module pattern” is somewhat unfortunate now that ECMAScript 2015 formally supports modules in a manner different from what the module pattern prescribes. For this reason, TypeScript uses the term “namespace” for its formalization of the module pattern.)
This section and the namespace section below will show how TypeScript emits consistent, idiomatic JavaScript when emitting ECMAScript 3 or 5 compliant code for classes and namespaces. The goal of TypeScript’s translation is to emit exactly what a programmer would type when implementing a class or namespace unaided by a tool. This section will also describe how TypeScript infers a type for each class declaration. We’ll start with a simple BankAccount class.
class BankAccount {
balance = 0;
deposit(credit: number) {
this.balance += credit;
return this.balance;
}
}
This class generates the following JavaScript code.
var BankAccount = (function () {
function BankAccount() {
this.balance = 0;
}
BankAccount.prototype.deposit = function(credit) {
this.balance += credit;
return this.balance;
};
return BankAccount;
})();
This TypeScript class declaration creates a variable named ‘BankAccount’ whose value is the constructor function for ‘BankAccount’ instances. This declaration also creates an instance type of the same name. If we were to write this type as an interface it would look like the following.
interface BankAccount {
balance: number;
deposit(credit: number): number;
}
If we were to write out the function type declaration for the ‘BankAccount’ constructor variable, it would have the following form.
var BankAccount: new() => BankAccount;
The function signature is prefixed with the keyword ‘new’ indicating that the ‘BankAccount’ function must be called as a constructor. It is possible for a function’s type to have both call and constructor signatures. For example, the type of the built-in JavaScript Date object includes both kinds of signatures.
If we want to start our bank account with an initial balance, we can add to the ‘BankAccount’ class a constructor declaration.
class BankAccount {
balance: number;
constructor(initially: number) {
this.balance = initially;
}
deposit(credit: number) {
this.balance += credit;
return this.balance;
}
}
This version of the ‘BankAccount’ class requires us to introduce a constructor parameter and then assign it to the ‘balance’ field. To simplify this common case, TypeScript accepts the following shorthand syntax.
class BankAccount {
constructor(public balance: number) {
}
deposit(credit: number) {
this.balance += credit;
return this.balance;
}
}
The ‘public’ keyword denotes that the constructor parameter is to be retained as a field. Public is the default accessibility for class members, but a programmer can also specify private or protected accessibility for a class member. Accessibility is a design-time construct; it is enforced during static type checking but does not imply any runtime enforcement.
TypeScript classes also support inheritance, as in the following example.
class CheckingAccount extends BankAccount {
constructor(balance: number) {
super(balance);
}
writeCheck(debit: number) {
this.balance -= debit;
}
}
In this example, the class ‘CheckingAccount’ derives from class ‘BankAccount’. The constructor for ‘CheckingAccount’ calls the constructor for class ‘BankAccount’ using the ‘super’ keyword. In the emitted JavaScript code, the prototype of ‘CheckingAccount’ will chain to the prototype of ‘BankAccount’.
TypeScript classes may also specify static members. Static class members become properties of the class constructor.
Section 8 provides additional information about classes.