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

TypeScript类:从基础到高级的全面指南

2024-05-217.3k 阅读

TypeScript 类基础

在 TypeScript 中,类是一种面向对象编程的基础结构,用于封装数据和行为。与 JavaScript 相比,TypeScript 通过类引入了更严格的类型检查和面向对象特性。

类的定义

定义一个简单的类,如下所示:

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,类型分别为 stringnumberconstructor 是类的构造函数,用于初始化对象的属性。greet 是一个实例方法,用于返回问候语。

创建类的实例

定义好类后,可以通过 new 关键字创建类的实例:

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

这里创建了 Person 类的一个实例 john,并调用了 greet 方法。

访问修饰符

TypeScript 支持三种访问修饰符:publicprivateprotected

public

public 是默认的访问修饰符。标记为 public 的属性或方法可以在类的内部和外部访问。

class Animal {
    public name: string;

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

    public eat() {
        console.log(`${this.name} is eating.`);
    }
}

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

在这个例子中,name 属性和 eat 方法都是 public 的,所以可以在类外部访问。

private

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

class Secret {
    private message: string;

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

    private showSecret() {
        console.log(this.message);
    }

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

let secret = new Secret('This is a secret');
// console.log(secret.message); // 报错,message 是 private 属性
// secret.showSecret(); // 报错,showSecret 是 private 方法
secret.reveal();

这里 message 属性和 showSecret 方法是 private 的,不能在类外部直接访问。只能通过类内部的 reveal 方法间接访问 showSecret 方法。

protected

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

class Vehicle {
    protected brand: string;

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

    protected describe() {
        return `This vehicle is a ${this.brand}`;
    }
}

class Car extends Vehicle {
    model: string;

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

    showDetails() {
        let description = super.describe();
        return `${description}, model ${this.model}`;
    }
}

let myCar = new Car('Toyota', 'Corolla');
// console.log(myCar.brand); // 报错,brand 是 protected 属性
// myCar.describe(); // 报错,describe 是 protected 方法
console.log(myCar.showDetails());

在这个例子中,Vehicle 类的 brand 属性和 describe 方法是 protected 的。Car 类继承自 Vehicle 类,可以在 Car 类内部访问 branddescribe

类的继承

继承是面向对象编程的重要特性之一,允许一个类继承另一个类的属性和方法。

基本继承

通过 extends 关键字实现继承:

class Shape {
    color: string;

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

    getColor() {
        return this.color;
    }
}

class Circle extends Shape {
    radius: number;

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

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

let redCircle = new Circle('red', 5);
console.log(redCircle.getColor());
console.log(redCircle.getArea());

这里 Circle 类继承自 Shape 类,拥有 Shape 类的 color 属性和 getColor 方法,同时还有自己的 radius 属性和 getArea 方法。

super 关键字

super 关键字在子类中有两个主要用途:调用父类的构造函数和访问父类的属性和方法。

class Animal {
    name: string;

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

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

class Dog extends Animal {
    breed: string;

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

    speak() {
        return `${super.speak()} It's a ${this.breed} dog.`;
    }
}

let buddy = new Dog('Buddy', 'Golden Retriever');
console.log(buddy.speak());

Dog 类的构造函数中,使用 super(name) 调用父类 Animal 的构造函数来初始化 name 属性。在 speak 方法中,使用 super.speak() 调用父类的 speak 方法,并在此基础上添加了子类特有的信息。

静态属性和方法

静态属性和方法属于类本身,而不是类的实例。通过 static 关键字定义。

静态属性

class MathUtils {
    static PI = 3.14159;

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

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

在这个例子中,PIMathUtils 类的静态属性,calculateCircleArea 是静态方法。可以直接通过类名来访问静态属性和方法,而不需要创建类的实例。

静态方法

静态方法通常用于执行与类相关但不依赖于特定实例的操作。例如,工具类中的一些通用计算方法。

class StringUtils {
    static capitalizeFirstLetter(str: string) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }
}

let result = StringUtils.capitalizeFirstLetter('hello');
console.log(result);

这里 capitalizeFirstLetterStringUtils 类的静态方法,用于将字符串的首字母大写。

抽象类

抽象类是一种不能被实例化的类,它主要用于定义一些抽象方法,让子类去实现这些方法。通过 abstract 关键字定义。

定义抽象类

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

class Rectangle extends Figure {
    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 figure = new Figure(); // 报错,不能实例化抽象类
let rectangle = new Rectangle(5, 3);
console.log(rectangle.getArea());
console.log(rectangle.getPerimeter());

在这个例子中,Figure 是抽象类,包含两个抽象方法 getAreagetPerimeterRectangle 类继承自 Figure 类,并实现了这两个抽象方法。

抽象方法

抽象方法只有方法签名,没有方法体,必须在子类中实现。抽象类可以包含抽象方法和具体方法。

abstract class Animal {
    name: string;

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

    abstract makeSound(): void;

    move() {
        console.log(`${this.name} is moving.`);
    }
}

class Cat extends Animal {
    makeSound() {
        console.log('Meow');
    }
}

let cat = new Cat('Whiskers');
cat.makeSound();
cat.move();

这里 Animal 类的 makeSound 是抽象方法,move 是具体方法。Cat 类继承自 Animal 类并实现了 makeSound 方法。

类与接口

接口在 TypeScript 中用于定义对象的形状,类可以实现接口,确保类具有接口所定义的属性和方法。

类实现接口

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

let circle = new Circle(5);
circle.draw();

在这个例子中,Drawable 接口定义了 draw 方法。Circle 类实现了 Drawable 接口,所以必须提供 draw 方法的具体实现。

多个接口实现

一个类可以实现多个接口,用逗号分隔。

interface Printable {
    print(): void;
}

interface Resizable {
    resize(factor: number): void;
}

class Rectangle implements Printable, Resizable {
    width: number;
    height: number;

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

    print() {
        console.log(`Rectangle: width ${this.width}, height ${this.height}`);
    }

    resize(factor: number) {
        this.width *= factor;
        this.height *= factor;
    }
}

let rectangle = new Rectangle(4, 3);
rectangle.print();
rectangle.resize(2);
rectangle.print();

这里 Rectangle 类实现了 PrintableResizable 两个接口,必须实现这两个接口定义的所有方法。

类的高级特性

类的类型别名

可以使用类型别名来为类定义一个更简洁的类型表示。

class Person {
    name: string;
    age: number;

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

type PersonType = Person;

let john: PersonType = new Person('John', 30);

这里 PersonTypePerson 类的类型别名,使用 PersonType 定义变量和使用 Person 类定义变量效果是一样的。

泛型类

泛型类允许我们在定义类时使用类型参数,使得类可以处理不同类型的数据,而不需要为每种类型都定义一个单独的类。

class Box<T> {
    contents: T;

    constructor(value: T) {
        this.contents = value;
    }

    getContents() {
        return this.contents;
    }
}

let numberBox = new Box<number>(42);
let stringBox = new Box<string>('Hello');

console.log(numberBox.getContents());
console.log(stringBox.getContents());

在这个例子中,Box 类是一个泛型类,T 是类型参数。可以在创建 Box 实例时指定具体的类型,如 numberstring

装饰器

装饰器是一种在类的声明及成员上添加标注的方式,用于修改类的行为或添加额外的功能。在 TypeScript 中,装饰器是一种实验性特性,需要在 tsconfig.json 中开启 experimentalDecorators 选项。

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
        console.log(`Calling method ${propertyKey} with arguments:`, args);
        const result = originalMethod.apply(this, args);
        console.log(`Method ${propertyKey} returned:`, result);
        return result;
    };

    return descriptor;
}

class Calculator {
    @log
    add(a: number, b: number) {
        return a + b;
    }
}

let calculator = new Calculator();
calculator.add(2, 3);

在这个例子中,log 是一个装饰器函数,它接收 target(类的原型对象)、propertyKey(方法名)和 descriptor(方法的属性描述符)作为参数。在 Calculator 类的 add 方法上使用 @log 装饰器,当调用 add 方法时,会在控制台打印方法调用的参数和返回值。

混入(Mixins)

混入是一种将多个类的功能合并到一个类中的技术。虽然 TypeScript 本身没有内置对混入的支持,但可以通过一些技巧来实现类似的功能。

class A {
    aMethod() {
        return 'a method';
    }
}

class B {
    bMethod() {
        return 'b method';
    }
}

function mixin(target: any, source: any) {
    Object.getOwnPropertyNames(source.prototype).forEach(name => {
        Object.defineProperty(
            target.prototype,
            name,
            Object.getOwnPropertyDescriptor(source.prototype, name) || Object.create(null)
        );
    });
    return target;
}

class C extends mixin(class {}, A) extends mixin(class {}, B) {}

let c = new C();
console.log(c.aMethod());
console.log(c.bMethod());

在这个例子中,mixin 函数将 AB 类的方法混入到 C 类中,使得 C 类可以使用 AB 类的方法。

类在前端开发中的应用场景

组件化开发

在前端框架如 React 和 Vue 中,虽然它们有自己的组件定义方式,但 TypeScript 的类可以用于封装组件的逻辑和状态。例如,在 React 中使用 TypeScript 类组件:

import React, { Component } from'react';

interface CounterProps {
    initialValue: number;
}

interface CounterState {
    value: number;
}

class Counter extends Component<CounterProps, CounterState> {
    constructor(props: CounterProps) {
        super(props);
        this.state = {
            value: props.initialValue
        };
    }

    increment = () => {
        this.setState(prevState => ({ value: prevState.value + 1 }));
    };

    render() {
        return (
            <div>
                <p>Value: {this.state.value}</p>
                <button onClick={this.increment}>Increment</button>
            </div>
        );
    }
}

export default Counter;

这里 Counter 类继承自 React.Component,定义了 propsstate 的类型,并实现了组件的渲染逻辑和状态更新方法。

数据模型封装

在处理复杂的数据逻辑时,可以使用类来封装数据模型及其相关操作。例如,在一个电商应用中,可以定义一个 Product 类:

class Product {
    id: number;
    name: string;
    price: number;
    description: string;

    constructor(id: number, name: string, price: number, description: string) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.description = description;
    }

    getDetails() {
        return `Name: ${this.name}, Price: ${this.price}, Description: ${this.description}`;
    }
}

let product = new Product(1, 'Smartphone', 599, 'A high - end smartphone');
console.log(product.getDetails());

Product 类封装了产品的数据和获取产品详细信息的方法,使得数据管理和操作更加清晰。

模块封装

将相关的功能封装在类中,并通过模块进行管理。例如,创建一个 Utils 模块,包含一些通用的工具类:

// utils.ts
export class StringUtils {
    static capitalizeFirstLetter(str: string) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }
}

export class MathUtils {
    static add(a: number, b: number) {
        return a + b;
    }
}

// main.ts
import { StringUtils, MathUtils } from './utils';

let capitalized = StringUtils.capitalizeFirstLetter('hello');
let sum = MathUtils.add(3, 4);

console.log(capitalized);
console.log(sum);

在这个例子中,StringUtilsMathUtils 类封装在 utils.ts 模块中,在 main.ts 中通过 import 引入并使用。

总结

TypeScript 的类提供了丰富的面向对象编程特性,从基础的类定义、属性和方法,到高级的继承、静态成员、抽象类、接口实现、泛型类、装饰器和混入等。这些特性使得代码更加结构化、可维护和可扩展,在前端开发中有着广泛的应用场景,如组件化开发、数据模型封装和模块管理等。通过合理运用 TypeScript 类的各种特性,可以提高前端代码的质量和开发效率。