May 07, 2021 TypeScript
Traditional JavaScript programs use functions and prototype-based inheritance to create reusable components, but it can be tricky for programmers familiar with using object-oriented methods, because they use class-based inheritance and objects are built from classes starting with ECMAScript 2015, or ECMAScript 6, and JavaScript programmers will be able to use class-based object-oriented methods. With TypeScript, we allow developers to use these features now, and compiled JavaScript can run on all major browsers and platforms without waiting for the next version of JavaScript.
Here's an example of using a class:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
If you've ever used C# or Java, you'll be familiar with this syntax. W
e declare a
Greeter
class. T
his class has three members: a property
called greeting, a constructor, and a
greet
greeting
method.
You'll notice that we use
this when we reference any class
this
member. I
t means that we are visiting members of the class.
On the last line, we constructed
an instance
of the
Greeter
class
new
using new. I
t calls the previously defined constructor, creates a
Greeter
type, and initializes it by executing the constructor.
In TypeScript, we can use the usual object-oriented patterns. Of course, the most basic pattern in class-based program design is to allow inheritance to be used to extend existing classes.
Take a look at the following example:
class Animal {
name:string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
This example shows some of the characteristics inherited in TypeScript that are similar to other languages. W
e use
extends
keyword to create sub-classes.
You can see
Horse
and
Snake
classes are
Animal
of the base class Animal and have access to their properties and methods.
The derived class that contains the constructor must
super()
executes the construction method of the base class.
This example shows how you can override the parent class in a child class.
Snake
the Snake class and the
Horse
class
move
methods that overrate the move method
move
Animal
move
method functional for different classes.
Note that even
tom
is declared
Animal
type, because its value is
Horse
tom.move(34)
Horse
method in Horse:
Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.
public
In the example above, we are free to access the members defined in the program. I
f you know more about classes in other
public
you'll notice that we
public
decoration in previous code;
In TypeScript, members default to
public
You can also explicitly mark a member as
public
We can rewrite the Animal class above in
Animal
way:
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
private
When a member is marked
private
it cannot have external access to the class that declared it.
Like what:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // Error: 'name' is private;
TypeScript uses a structural type system. When we compare two different types, we don't care where they come from, and if all member types are compatible, we think their types are compatible.
However, when we compare types with
private
or
protected
members, the situation is different. I
f one of the types contains
private
we consider the two
private
compatible only if such a private member also exists in the other type, and they are both from the same declaration.
This
protected
used for protected members.
Here's an example that better illustrates this:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
animal = employee; // Error: Animal and Employee are not compatible
In this example,
Animal
two
Rhino
and
Rhino
are
Animal
of the Animal class. T
here is also an
Employee
class that looks the same type
Animal
Animal. W
e created several instances of these classes and assigned each other to see what happens. B
ecause
Animal
and
Rhino
share private member
Animal
from
private name: string
they are compatible. E
mployee,
Employee
is not. W
hen you
Employee
to
Animal
you get an error that says their types are incompatible.
Although
Employee
is a private member name in
name
is clearly not the
Animal
in Animal.
protected
protected
modifier
private
to the private modifier, but with one difference,
protected
still accessible in the derived class.
For example:
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name)
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // error
Note that we
Person
outside of the Person
name
but
Employee
through the instance method of the Employee class because
Employee
is derived from
Person
Constructors can also be marked
protected
T
his means that the class cannot be instantiated outside the class that contains it, but can be inherited.
Like what
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee can extend Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected
You can use
readonly
keyword to make properties read-only.
Read-only properties must be initialized at the time of declaration or in the constructor.
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.
In the example above, we had to define a
name
and
theName
Person
class,
name
theName
T
his is often the case.
ties
make it easy for us to define and initialize a member in one place.
The following example is a
Animal
version of the previous Animal class, using parameter properties:
class Animal {
constructor(private name: string) { }
move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
Notice how we abandoned
theName
the private name: string parameter only in the
private name: string
to create and initialize
name
members.
We combine declarations and assignments into one place.
Argument properties are declared by adding an access qualifier to the constructor argument.
private
to qualify a parameter property
public
protected
private member;
TypeScript supports intercepting access to object members through getters/setters. It helps you effectively control access to object members.
Here's how to rewrite a simple class to use
get
and
set
First, let's start with an example where the accessor is not used.
class Employee {
fullName: string;
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
console.log(employee.fullName);
}
We can set
fullName
which is very convenient, but it can also cause trouble.
In the following version, we check that the user password is correct before allowing it to modify employee information. W
e changed the
fullName
to a set method that checks
set
We've also added
get
method so that the example above still works.
let passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
alert(employee.fullName);
}
We can change the password to verify that the accessor works. When the password is not correct, we are prompted that we do not have permission to modify the employee.
There are a few points to note about accessors:
First, the accessor requires you to set the compiler to output ECMAScript 5 or higher. D
emotion to ECMAScript 3 is not supported. S
econd, accessors
get
without
set
are automatically inferred as
readonly
This is helpful when generating
.d.ts
because users who take advantage of this property will see that enough values are not allowed to change it.
So far, we've only talked about instance members of classes, properties that are initialized only when the class is instantiated. W
e can also create static members of a class that exist above the class itself rather than on the instance of the class. I
n this example, we use
static
define
origin
because it is a property that all grids use. W
hen each instance wants to access this property, add a class name before
origin
I
t's like using this. on
this.
T
he prefix to access properties is the same as here we
Grid.
to access static properties.
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor (public scale: number) { }
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
Abstract classes are used as base classes for other derived classes. T
hey are generally not instantiated directly. U
nlike interfaces, abstract classes can contain the implementation details of members.
abstract
keywords are used to define abstract classes and to define abstract methods within abstract classes.
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
Abstract methods in abstract classes do not contain concrete implementations and must be implemented in derived classes. T
he syntax of abstract methods is similar to that of interface methods. B
oth define method signatures but do not contain method bodies.
However, abstract methods must
abstract
keywords and can contain access modifiers.
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // constructors in derived classes must call super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type
When you declare a class in TypeScript, you actually declare a lot of things at the same time. The first is the type of instance of the class.
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
Here, we write
let greeter: Greeter
which means that the type of instance of the
Greeter
class
Greeter
This is an old habit for programmers who have used other object-oriented languages.
We also created a value
called
constructor. T
his function is called when we
create a class instance using
new
new.
Let's take a look at what the above code looks like when compiled into JavaScript:
let Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
})();
let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
In the code above,
let Greeter
is assigned as a constructor. W
hen we call
new
and execute
this function, we get an instance of the class. T
his constructor also contains all the static properties of the class.
T
o put it another way, we can think of the
class
as
having
both
the instance
part and the static
part.
Let's rewrite this example a little bit to see the differences before them:
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
}
else {
return Greeter.standardGreeting;
}
}
}
let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
In this example,
greeter1
is the same as you saw earlier. W
e instantiate
Greeter
class and use this object. I
t's the same as we've seen before.
After that, let's go straight to the class. W
e created 33 a variable called
greeterMaker
T
his variable saves the class or saves the class constructor.
T
hen we use
typeof Greeter
means the type of the
waiter class, not the type of the instance. O
r rather, "I tell the type of
Greeter
identifier," which is the type of constructor. T
his type contains all static members and constructors of the
class. T
hen, just like before, we use
new on
greeterMaker
to
new
an instance of
33
Greeter
Greeter.
As mentioned in the last section, the class definition creates two things: the instance type of the class and a constructor. Because classes can create types, you can use classes where interfaces are allowed.
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};