MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TypeScript 类定义与 class 关键字的使用指南

2021-11-194.9k 阅读

一、TypeScript 类的基本概念

在 TypeScript 中,类(class)是一种面向对象编程(OOP)的基础结构,它允许我们定义对象的属性和行为。类就像是一个蓝图,用于创建具有相同结构和行为的对象实例。通过类,我们可以实现封装、继承和多态等 OOP 的核心特性。

1.1 类的定义基础语法

使用 class 关键字来定义一个类。以下是一个简单的类定义示例:

class Person {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    greet() {
        return `Hello, my name is ${this.name} and I'm ${this.age} years old.`;
    }
}

在上述代码中:

  • 首先定义了一个名为 Person 的类。
  • 类中有两个属性 nameage,分别为字符串类型和数字类型。
  • constructor 是类的构造函数,用于初始化类的实例。当使用 new 关键字创建类的实例时,构造函数会被调用。构造函数接收两个参数 nameage,并将它们赋值给类的属性。
  • greet 是类的一个方法,它返回一个问候语字符串,使用了类的属性 nameage

1.2 创建类的实例

定义好类后,我们可以使用 new 关键字来创建类的实例:

let john = new Person('John', 30);
console.log(john.greet()); 

在上述代码中,new Person('John', 30) 创建了 Person 类的一个实例,并将其赋值给变量 john。然后调用 johngreet 方法,输出问候语。

二、类的属性修饰符

TypeScript 提供了几种属性修饰符,用于控制类属性的访问权限和行为。

2.1 public 修饰符

public 是默认的属性修饰符,如果没有显式指定修饰符,属性默认为 publicpublic 属性可以在类的内部和外部访问。

class Animal {
    public name: string;

    constructor(name: string) {
        this.name = name;
    }

    public speak() {
        return `${this.name} makes a sound.`;
    }
}

let dog = new Animal('Buddy');
console.log(dog.name); 
console.log(dog.speak()); 

在上述代码中,name 属性和 speak 方法都是 public 的,因此可以在类的外部访问。

2.2 private 修饰符

private 修饰的属性或方法只能在类的内部访问,外部无法访问。

class Secret {
    private code: string;

    constructor(code: string) {
        this.code = code;
    }

    private reveal() {
        return `The secret code is ${this.code}`;
    }

    public showSecret() {
        return this.reveal();
    }
}

let secret = new Secret('12345');
// console.log(secret.code); // 这行代码会报错,因为 code 是 private 属性
console.log(secret.showSecret()); 

在上述代码中,code 属性和 reveal 方法是 private 的,外部无法直接访问。但是可以通过类内部的 public 方法 showSecret 间接访问 private 方法 reveal

2.3 protected 修饰符

protected 修饰的属性或方法可以在类的内部以及子类中访问,但在类的外部无法访问。

class Shape {
    protected color: string;

    constructor(color: string) {
        this.color = color;
    }

    protected getColor() {
        return this.color;
    }
}

class Circle extends Shape {
    radius: number;

    constructor(color: string, radius: number) {
        super(color);
        this.radius = radius;
    }

    describe() {
        return `This is a ${this.getColor()} circle with radius ${this.radius}`;
    }
}

let circle = new Circle('red', 5);
// console.log(circle.color); // 这行代码会报错,因为 color 是 protected 属性
console.log(circle.describe()); 

在上述代码中,Shape 类的 color 属性和 getColor 方法是 protected 的。Circle 类继承自 Shape 类,在 Circle 类内部可以访问 protected 的属性和方法,但在外部无法访问。

2.4 readonly 修饰符

readonly 修饰的属性只能在声明时或构造函数中赋值,之后不能再修改。

class Car {
    readonly brand: string;
    model: string;

    constructor(brand: string, model: string) {
        this.brand = brand;
        this.model = model;
    }
}

let myCar = new Car('Toyota', 'Corolla');
// myCar.brand = 'Honda'; // 这行代码会报错,因为 brand 是 readonly 属性
console.log(myCar.brand); 

在上述代码中,brand 属性是 readonly 的,一旦在构造函数中赋值后,就不能再修改。

三、类的继承

继承是面向对象编程中的一个重要特性,它允许一个类从另一个类中获取属性和方法。在 TypeScript 中,使用 extends 关键字来实现继承。

3.1 基本继承示例

class Vehicle {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    move(distance: number) {
        return `${this.name} moves ${distance} meters.`;
    }
}

class Car extends Vehicle {
    wheels: number;

    constructor(name: string, wheels: number) {
        super(name);
        this.wheels = wheels;
    }

    drive() {
        return `${this.name} with ${this.wheels} wheels is driving.`;
    }
}

let myCar = new Car('Sedan', 4);
console.log(myCar.move(100)); 
console.log(myCar.drive()); 

在上述代码中,Car 类继承自 Vehicle 类。Car 类不仅拥有自己的属性 wheels 和方法 drive,还继承了 Vehicle 类的属性 name 和方法 movesuper 关键字用于调用父类的构造函数,确保父类的属性正确初始化。

3.2 重写父类方法

子类可以重写父类的方法,以提供更具体的实现。

class Animal {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    speak() {
        return `${this.name} makes a sound.`;
    }
}

class Dog extends Animal {
    constructor(name: string) {
        super(name);
    }

    speak() {
        return `${this.name} barks.`;
    }
}

let dog = new Dog('Buddy');
console.log(dog.speak()); 

在上述代码中,Dog 类重写了 Animal 类的 speak 方法,提供了狗叫的具体实现。

3.3 super 关键字的使用

super 关键字不仅用于调用父类的构造函数,还可以用于调用父类的方法。

class Shape {
    color: string;

    constructor(color: string) {
        this.color = color;
    }

    describe() {
        return `This shape is ${this.color}.`;
    }
}

class Rectangle extends Shape {
    width: number;
    height: number;

    constructor(color: string, width: number, height: number) {
        super(color);
        this.width = width;
        this.height = height;
    }

    describe() {
        let baseDescription = super.describe();
        return `${baseDescription} It has width ${this.width} and height ${this.height}.`;
    }
}

let rectangle = new Rectangle('blue', 5, 10);
console.log(rectangle.describe()); 

在上述代码中,Rectangle 类的 describe 方法首先调用了父类 Shapedescribe 方法,获取基本描述,然后再添加自己特有的描述信息。

四、类的静态成员

静态成员是属于类本身而不是类的实例的属性和方法。在 TypeScript 中,使用 static 关键字来定义静态成员。

4.1 静态属性

class MathUtils {
    static PI: number = 3.14159;

    static calculateCircleArea(radius: number) {
        return MathUtils.PI * radius * radius;
    }
}

console.log(MathUtils.PI); 
console.log(MathUtils.calculateCircleArea(5)); 

在上述代码中,PIMathUtils 类的静态属性,calculateCircleArea 是静态方法。可以通过类名直接访问静态成员,而不需要创建类的实例。

4.2 静态方法

静态方法通常用于执行与类相关但不依赖于特定实例的操作。

class User {
    static counter: number = 0;
    name: string;

    constructor(name: string) {
        this.name = name;
        User.counter++;
    }

    static getTotalUsers() {
        return User.counter;
    }
}

let user1 = new User('Alice');
let user2 = new User('Bob');
console.log(User.getTotalUsers()); 

在上述代码中,counter 是静态属性,用于记录创建的 User 实例数量。getTotalUsers 是静态方法,用于获取总的用户数量。每次创建新的 User 实例时,静态属性 counter 会增加。

五、抽象类

抽象类是一种不能被实例化的类,它主要用于作为其他类的基类,为子类提供一个通用的接口。在 TypeScript 中,使用 abstract 关键字来定义抽象类和抽象方法。

5.1 抽象类定义

abstract class Shape {
    abstract getArea(): number;
    abstract getPerimeter(): number;
}

class Circle extends Shape {
    radius: number;

    constructor(radius: number) {
        super();
        this.radius = radius;
    }

    getArea() {
        return Math.PI * this.radius * this.radius;
    }

    getPerimeter() {
        return 2 * Math.PI * this.radius;
    }
}

class Rectangle extends Shape {
    width: number;
    height: number;

    constructor(width: number, height: number) {
        super();
        this.width = width;
        this.height = height;
    }

    getArea() {
        return this.width * this.height;
    }

    getPerimeter() {
        return 2 * (this.width + this.height);
    }
}

// let shape = new Shape(); // 这行代码会报错,因为 Shape 是抽象类,不能被实例化
let circle = new Circle(5);
let rectangle = new Rectangle(4, 6);
console.log(circle.getArea()); 
console.log(rectangle.getPerimeter()); 

在上述代码中,Shape 是抽象类,包含两个抽象方法 getAreagetPerimeter。抽象方法只有声明,没有实现。CircleRectangle 类继承自 Shape 类,并实现了抽象方法。

5.2 抽象类的作用

抽象类提供了一种契约,规定了子类必须实现某些方法。这有助于确保子类具有一致的接口,同时也提供了代码的可扩展性和维护性。例如,在图形绘制的应用中,可以定义一个抽象的 Shape 类,然后有各种具体的形状类如 CircleRectangle 等继承自它,每个具体类根据自身特点实现 getAreagetPerimeter 方法。

六、类与接口的关系

6.1 类实现接口

接口定义了一组属性和方法的签名,类可以实现接口,以确保满足接口的要求。

interface Printable {
    print(): void;
}

class Book implements Printable {
    title: string;

    constructor(title: string) {
        this.title = title;
    }

    print() {
        console.log(`Book title: ${this.title}`);
    }
}

let book = new Book('TypeScript in Action');
book.print(); 

在上述代码中,Printable 是一个接口,定义了 print 方法。Book 类实现了 Printable 接口,并提供了 print 方法的具体实现。

6.2 接口继承接口

接口可以继承其他接口,以扩展接口的功能。

interface Shape {
    getArea(): number;
}

interface Colorful {
    color: string;
}

interface ColoredShape extends Shape, Colorful {
    getPerimeter(): number;
}

class Square implements ColoredShape {
    side: number;
    color: string;

    constructor(side: number, color: string) {
        this.side = side;
        this.color = color;
    }

    getArea() {
        return this.side * this.side;
    }

    getPerimeter() {
        return 4 * this.side;
    }
}

let square = new Square(5, 'red');
console.log(square.getArea()); 
console.log(square.color); 

在上述代码中,ColoredShape 接口继承了 ShapeColorful 接口,同时定义了自己的 getPerimeter 方法。Square 类实现了 ColoredShape 接口,需要实现所有相关的方法和属性。

6.3 类与接口的多态

接口和类的结合可以实现多态。例如,定义一个接受实现了特定接口的对象的函数,不同的类实现该接口,从而在函数中表现出不同的行为。

interface Drawable {
    draw(): void;
}

class Circle implements Drawable {
    radius: number;

    constructor(radius: number) {
        this.radius = radius;
    }

    draw() {
        console.log(`Drawing a circle with radius ${this.radius}`);
    }
}

class Rectangle implements Drawable {
    width: number;
    height: number;

    constructor(width: number, height: number) {
        this.width = width;
        this.height = height;
    }

    draw() {
        console.log(`Drawing a rectangle with width ${this.width} and height ${this.height}`);
    }
}

function drawShapes(shapes: Drawable[]) {
    shapes.forEach(shape => shape.draw());
}

let circle = new Circle(5);
let rectangle = new Rectangle(4, 6);
drawShapes([circle, rectangle]); 

在上述代码中,Drawable 接口定义了 draw 方法。CircleRectangle 类都实现了 Drawable 接口。drawShapes 函数接受一个 Drawable 类型的数组,遍历数组并调用每个对象的 draw 方法,不同的类表现出不同的绘制行为,这就是多态的体现。

七、类的高级特性

7.1 存取器(Accessor)

存取器提供了一种更灵活的方式来访问和修改类的属性,通过 getset 关键字实现。

class Temperature {
    private _celsius: number;

    constructor(celsius: number) {
        this._celsius = celsius;
    }

    get fahrenheit() {
        return (this._celsius * 1.8) + 32;
    }

    set fahrenheit(value: number) {
        this._celsius = (value - 32) / 1.8;
    }
}

let temp = new Temperature(25);
console.log(temp.fahrenheit); 
temp.fahrenheit = 77;
console.log(temp._celsius); 

在上述代码中,Temperature 类有一个私有属性 _celsius。通过 getset 关键字定义了 fahrenheit 存取器。get 方法用于获取华氏温度,set 方法用于设置华氏温度,同时更新对应的摄氏温度。

7.2 类的类型兼容性

TypeScript 在进行类型检查时,对于类的兼容性有特定的规则。类的兼容性基于结构,而不是基于继承关系。

class A {
    x: number;
}

class B {
    x: number;
    y: number;
}

let a: A = new A();
let b: B = new B();
a = b; 
// b = a; // 这行代码会报错,因为 A 缺少 B 中的 y 属性

在上述代码中,B 类的实例可以赋值给 A 类的变量,因为 B 类包含了 A 类的所有属性。但反过来不行,因为 A 类缺少 B 类的 y 属性。

7.3 混合类型

类可以实现多个接口,从而具有多种类型,这称为混合类型。

interface Logger {
    log(message: string): void;
}

interface Printer {
    print(data: any): void;
}

class Utility implements Logger, Printer {
    log(message: string) {
        console.log(`Log: ${message}`);
    }

    print(data: any) {
        console.log(`Print: ${JSON.stringify(data)}`);
    }
}

let utility = new Utility();
utility.log('This is a log message');
utility.print({ key: 'value' }); 

在上述代码中,Utility 类实现了 LoggerPrinter 接口,因此它既可以作为 Logger 类型使用,也可以作为 Printer 类型使用,展现出混合类型的特性。

通过深入理解和运用 TypeScript 中类的各种特性,开发人员能够编写出更健壮、可维护和可扩展的前端代码,充分发挥面向对象编程在前端开发中的优势。无论是小型项目还是大型企业级应用,合理使用类和相关特性都能提升代码质量和开发效率。