TypeScript中类的全面剖析与继承机制
类的基础概念与定义
在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
类。首先声明了两个属性name
和age
,它们分别是string
类型和number
类型。constructor
是类的构造函数,用于在创建类的实例时初始化对象的属性。introduce
方法则是定义了Person
类的一个行为,返回一个自我介绍的字符串。
要创建Person
类的实例,可以使用以下代码:
let tom = new Person('Tom', 25);
console.log(tom.introduce());
类的访问修饰符
TypeScript提供了三种访问修饰符来控制类的属性和方法的访问权限,分别是public
、private
和protected
。
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
和一个具体方法printArea
。Rectangle
类继承自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));
在上述代码中,PI
是MathUtils
类的静态属性,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());
在上述代码中,userCount
是User
类的私有静态属性,用于记录创建的用户数量。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
方法时,会根据对象的实际类型(Circle
或Rectangle
)调用相应的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
是一个属性装饰器。它通过重新定义属性的getter
和setter
方法,确保属性值是大于0的数字。
通过以上对TypeScript中类的全面剖析以及继承机制、相关特性的介绍,我们可以看到TypeScript的类为开发者提供了强大的面向对象编程能力,帮助我们构建更加健壮、可维护和可扩展的前端应用程序。无论是小型项目还是大型企业级应用,合理运用类的各种特性都能极大地提升开发效率和代码质量。