Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

TypeScript class


May 07, 2021 TypeScript


Table of contents


Introduction to the Typing Class

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.

Class

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.

Inherited

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, private, and protected modifiers

The default is 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.`);
    }
}

Understand 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.

Understand 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

readonly modifier

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.

The argument property

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;

The accessor

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.

Static properties

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 class

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

Advanced skills

The constructor

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.

Use the class as an interface

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};