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

TypeScript中类的全面剖析与继承机制

2021-06-302.3k 阅读

类的基础概念与定义

在TypeScript中,类是一种面向对象编程的核心概念,它是对对象的抽象描述,定义了对象所具有的属性和方法。类就像是一个蓝图,通过它可以创建出多个具有相同属性和行为的对象实例。

在TypeScript中定义一个类非常直观,下面是一个简单的类定义示例:

class Person {
    name: string;
    age: number;

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

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

在上述代码中,我们定义了一个Person类。首先声明了两个属性nameage,它们分别是string类型和number类型。constructor是类的构造函数,用于在创建类的实例时初始化对象的属性。introduce方法则是定义了Person类的一个行为,返回一个自我介绍的字符串。

要创建Person类的实例,可以使用以下代码:

let tom = new Person('Tom', 25);
console.log(tom.introduce());

类的访问修饰符

TypeScript提供了三种访问修饰符来控制类的属性和方法的访问权限,分别是publicprivateprotected

public修饰符

public是默认的访问修饰符,如果没有明确指定修饰符,类的属性和方法默认都是public的。public修饰的属性和方法可以在类的内部、类的实例以及子类中被访问。

class Animal {
    public name: string;

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

    public makeSound() {
        console.log('Some sound');
    }
}

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

在上述代码中,name属性和makeSound方法都是public的,所以可以在类的外部通过实例进行访问。

private修饰符

private修饰的属性和方法只能在类的内部访问,在类的外部和子类中都无法访问。这有助于隐藏类的内部实现细节,提高代码的安全性和封装性。

class SecretiveClass {
    private secretValue: number;

    constructor(value: number) {
        this.secretValue = value;
    }

    private secretMethod() {
        console.log('This is a secret method with value:', this.secretValue);
    }

    public accessSecret() {
        this.secretMethod();
    }
}

let instance = new SecretiveClass(42);
// console.log(instance.secretValue); // 报错,无法访问私有属性
// instance.secretMethod(); // 报错,无法访问私有方法
instance.accessSecret();

在上述代码中,secretValue属性和secretMethod方法都是private的,外部无法直接访问。但是可以通过类内部的public方法accessSecret间接访问私有方法。

protected修饰符

protected修饰的属性和方法可以在类的内部以及子类中访问,但不能在类的外部通过实例访问。它在保护类的内部状态的同时,也允许子类对这些属性和方法进行访问和扩展。

class Shape {
    protected color: string;

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

    protected printColor() {
        console.log('The color of the shape is:', this.color);
    }
}

class Circle extends Shape {
    radius: number;

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

    public display() {
        this.printColor();
        console.log('The radius of the circle is:', this.radius);
    }
}

let circle = new Circle('red', 5);
// console.log(circle.color); // 报错,无法从外部访问protected属性
// circle.printColor(); // 报错,无法从外部访问protected方法
circle.display();

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

类的继承机制

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

简单继承示例

class Vehicle {
    brand: string;

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

    drive() {
        console.log(`Driving a ${this.brand} vehicle.`);
    }
}

class Car extends Vehicle {
    model: string;

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

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

let myCar = new Car('Toyota', 'Corolla');
myCar.drive();
console.log(myCar.describe());

在上述代码中,Car类继承自Vehicle类。Car类不仅拥有Vehicle类的brand属性和drive方法,还添加了自己的model属性和describe方法。在Car类的构造函数中,使用super关键字调用了Vehicle类的构造函数来初始化继承的brand属性。

重写继承的方法

子类可以重写从父类继承的方法,以实现不同的行为。

class Animal {
    makeSound() {
        console.log('Generic animal sound');
    }
}

class Dog extends Animal {
    makeSound() {
        console.log('Woof!');
    }
}

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

let dog = new Dog();
let cat = new Cat();

dog.makeSound();
cat.makeSound();

在上述代码中,Dog类和Cat类都继承自Animal类,并各自重写了makeSound方法,以实现特定的叫声。

访问父类的方法

在子类重写方法时,有时需要调用父类的方法来扩展或修改其行为。可以使用super关键字来调用父类的方法。

class Employee {
    name: string;
    salary: number;

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

    getDetails() {
        return `Name: ${this.name}, Salary: ${this.salary}`;
    }
}

class Manager extends Employee {
    department: string;

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

    getDetails() {
        let baseDetails = super.getDetails();
        return `${baseDetails}, Department: ${this.department}`;
    }
}

let manager = new Manager('John', 5000, 'HR');
console.log(manager.getDetails());

在上述代码中,Manager类继承自Employee类并重写了getDetails方法。在Manager类的getDetails方法中,首先使用super.getDetails()获取父类的详细信息,然后再添加自己的department信息。

抽象类与抽象方法

抽象类

抽象类是一种不能被实例化的类,它主要作为其他类的基类,为子类提供一个通用的框架。抽象类可以包含抽象方法和具体方法。在TypeScript中,使用abstract关键字来定义抽象类。

abstract class Shape {
    abstract area(): number;

    printArea() {
        console.log('The area of the shape is:', this.area());
    }
}

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

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

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

// let shape = new Shape(); // 报错,无法实例化抽象类
let rectangle = new Rectangle(5, 3);
rectangle.printArea();

在上述代码中,Shape类是一个抽象类,它包含一个抽象方法area和一个具体方法printAreaRectangle类继承自Shape类并实现了抽象方法area。由于Shape是抽象类,不能直接实例化。

抽象方法

抽象方法是在抽象类中声明但没有实现的方法,其具体实现由子类来完成。抽象方法必须在抽象类中定义,并且子类必须重写这些抽象方法。

abstract class Animal {
    abstract makeSound(): void;

    move() {
        console.log('Moving...');
    }
}

class Dog extends Animal {
    makeSound() {
        console.log('Woof!');
    }
}

class Bird extends Animal {
    makeSound() {
        console.log('Tweet!');
    }
}

let dog = new Dog();
let bird = new Bird();

dog.makeSound();
bird.makeSound();
dog.move();
bird.move();

在上述代码中,Animal类的makeSound方法是抽象方法,Dog类和Bird类继承自Animal类并分别实现了makeSound方法。同时,它们也继承了Animal类的具体方法move

类的静态成员

静态属性

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

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是静态方法。可以通过类名直接访问静态属性和静态方法,而不需要创建类的实例。

静态方法

静态方法同样属于类本身,常用于执行与类相关但不需要依赖类的实例的操作。

class User {
    private static userCount: number = 0;

    constructor() {
        User.userCount++;
    }

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

let user1 = new User();
let user2 = new User();

console.log(User.getTotalUsers());

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

类与接口的关系

类实现接口

接口定义了一组属性和方法的签名,但不包含具体的实现。类可以实现接口,以确保类具有接口所定义的属性和方法。在TypeScript中,使用implements关键字来实现接口。

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方法的具体实现。

接口继承接口与类继承接口

接口可以继承其他接口,以扩展功能。同时,类可以继承自一个类并实现多个接口。

interface Shape {
    area(): number;
}

interface Colorable {
    color: string;
}

interface ColoredShape extends Shape, Colorable {}

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

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

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

let square = new Square(5, 'blue');
console.log('Area:', square.area());
console.log('Color:', square.color);

在上述代码中,ColoredShape接口继承自Shape接口和Colorable接口。Square类实现了ColoredShape接口,需要同时实现Shape接口的area方法和满足Colorable接口的color属性要求。

类的多态性

多态性是指同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在TypeScript中,通过类的继承和方法重写来实现多态性。

class Shape {
    draw() {
        console.log('Drawing a shape');
    }
}

class Circle extends Shape {
    draw() {
        console.log('Drawing a circle');
    }
}

class Rectangle extends Shape {
    draw() {
        console.log('Drawing a rectangle');
    }
}

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

let circle = new Circle();
let rectangle = new Rectangle();

drawShapes([circle, rectangle]);

在上述代码中,Shape类有一个draw方法,Circle类和Rectangle类继承自Shape类并重写了draw方法。drawShapes函数接受一个Shape类型的数组,在遍历数组调用draw方法时,会根据对象的实际类型(CircleRectangle)调用相应的draw方法,体现了多态性。

类的装饰器

装饰器是一种特殊类型的声明,它可以被附加到类声明、方法、属性或参数上,用于修改类的行为或添加额外的功能。在TypeScript中,使用@符号来应用装饰器。

类装饰器

类装饰器应用于类的定义。它可以修改类的原型,添加新的属性或方法等。

function logClass(target: Function) {
    console.log('Class decorated:', target.name);
    return class extends target {
        newProperty = 'New property added by decorator';
        log() {
            console.log('This is a new method added by decorator');
        }
    };
}

@logClass
class MyClass {
    originalMethod() {
        console.log('This is the original method');
    }
}

let myClass = new MyClass();
myClass.originalMethod();
console.log(myClass.newProperty);
myClass.log();

在上述代码中,logClass是一个类装饰器。它在控制台打印被装饰类的名称,并返回一个新的类,这个新类继承自原类并添加了新的属性newProperty和方法log

方法装饰器

方法装饰器应用于类的方法。它可以修改方法的行为,例如添加日志记录、验证参数等。

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

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

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

在上述代码中,logMethod是一个方法装饰器。它在方法调用前后打印日志信息,记录方法名、参数和返回值。

属性装饰器

属性装饰器应用于类的属性。它可以用于验证属性值、添加属性访问控制等。

function validateProperty(target: any, propertyKey: string) {
    let value: any;
    const getter = function() {
        return value;
    };
    const setter = function(newValue: any) {
        if (typeof newValue === 'number' && newValue > 0) {
            value = newValue;
        } else {
            throw new Error('Invalid value');
        }
    };
    if (delete target[propertyKey]) {
        Object.defineProperty(target, propertyKey, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

class PositiveNumber {
    @validateProperty
    numberValue: number;

    constructor(value: number) {
        this.numberValue = value;
    }
}

let positiveNumber = new PositiveNumber(5);
console.log(positiveNumber.numberValue);
// positiveNumber.numberValue = -1; // 报错,Invalid value

在上述代码中,validateProperty是一个属性装饰器。它通过重新定义属性的gettersetter方法,确保属性值是大于0的数字。

通过以上对TypeScript中类的全面剖析以及继承机制、相关特性的介绍,我们可以看到TypeScript的类为开发者提供了强大的面向对象编程能力,帮助我们构建更加健壮、可维护和可扩展的前端应用程序。无论是小型项目还是大型企业级应用,合理运用类的各种特性都能极大地提升开发效率和代码质量。