0%

Learning TypeScript From Scratch(5)

Classes


Simple example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal {
move(distance: number = 0) {
console.log(`Animal moved ${distance}m.`);
}
}

class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}

const dog = new Dog();
dog.bark();
dog.move(10);

Super

  • super() calls the constructor function of the base class
  • each derived class must call super() when accessing a property and when inside the constructor function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distance: number = 0) {
console.log(`${this.name} moved ${distance}m.`)
}
}

class Snake extends Animal {
constructor(name: string) {
super(name);
}
move(distance = 5) {
console.log("Slithering...");
super.move(distance);
}
}

let sam = new Snake ("Sam");
sam.move();
// Slithering...
// Sam moved 5m.

let tom: Animal = new Snake("Tom");
tom.move(34);
// Slithering...
// Tom moved 34m.

Notice that even though tom is declared as an Animal, since its value is a Snake, calling tom.move(34) will call the overriding method in Snake.

Public

  • public is by default
1
2
3
4
5
class Animal {
public name: string;
public constructor(theName: string) { ... };
public move(distance: number) { ... };
}

Private

  • private property is only accessible within its class
  • two syntax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// General syntax
class Animal {
private name: string;
constructor(theName: string) { ... }
}
new Animal("Cat").name; // Error, cuz name is private in Animal

// Syntax that ECMAScript supports natively
class Animal {
#name: string;
constructor(theName: string) {
this.#name = theName;
}
}
new Animal("Cat").#name; // Error, cuz #name is private in Animal

Protected

  • protected property is accessible within its class and its deriving classes
  • constructor can also be protected, which means that the class cannot be instantiated outside of its containing class, but can be extended
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
protected name: sting;
protected constructor(name: string) {
this.name = name;
}
}

class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
// Notice that we can access the protected name here, in deriving class
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}

let tom = new Employee("Tom", "Sales");
console.log(tom.name); // Error, cuz name is protected in Person

let john = new Person("John"); // Error, cuz constructor of Person is protected

Readonly

  • readonly property must be initialized at their declaration or in the constructor
1
2
3
4
5
6
7
8
9
class Person {
readonly school: string = "SCUT";
readonly name: string;
constructor(theName: string) {
this.name = theName;
}
}
let maggie = new Person("Maggie");
maggie.name = "mag"; // Error, cuz name is readonly

Parameter property

  • we can use parameter properties to simplify the syntax, which lets us to create and initialize a member in one place
1
2
3
4
5
6
class Person {
readonly school: string = "SCUT";
constructor(readonly name: string, private age: number, protected address: string) {}
}
let maggie = new Person("Maggie", 18, "Guangzhou");
maggie.name;

Accessors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
private _name: string = "";

get name(): string {
return this._name;
}

set name(newName: string) {
if(newName && newName.length > 20) {
throw new Error("Length too long");
}
this._name = newName;
}
}

Static property

1
2
3
4
5
6
7
8
9
class Grid {
static origin = { x: 0, y: 0 };
justAFunction() {
...
// way to access static property
let { x, y } = Grid.origin;
...
}
}

Abstract

  • both class and method can be abstract
  • abstract class cannot have instances
  • abstract methods must be implemented in derived classes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
abstract class Department {
constructor(public name: string) {}
printName() {
console.log(`name: ${this.name}`);
}
abstract printMeeting(): void;
}

class AccountingDepartment extends Department {
constructor() {
super("Accounting");
}
printMeeting() {
console.log("Meeting of accounting department is at 10am");
}
generateReports() {
console.log("Generating accounting reports...");
}
}

let d: Department; // OK to create a reference to an abstract type
d = new Department(); // Error to create instance of abstract class
d = new AccountingDepartment(); // OK to create and assign a non-abstract subclass

d.printName(); // OK
d.printMeeting(); // OK
d.generateReports(); // Error, cuz generateReports does not exist on type Department

Using class as an interface

  • a class declaration creates two things:
    • a type representing instances of the class
    • a constructor function

As it creates types, so we could use class like interface.

1
2
3
4
5
6
7
8
9
10
class Point {
x: number;
y: number;
}

interface Point3d extends Point {
z: number;
}

let point3d: Point3d = { x: 1, y: 2, z: 3};