TypeScript 类定义与 class 关键字的使用指南
一、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
的类。 - 类中有两个属性
name
和age
,分别为字符串类型和数字类型。 constructor
是类的构造函数,用于初始化类的实例。当使用new
关键字创建类的实例时,构造函数会被调用。构造函数接收两个参数name
和age
,并将它们赋值给类的属性。greet
是类的一个方法,它返回一个问候语字符串,使用了类的属性name
和age
。
1.2 创建类的实例
定义好类后,我们可以使用 new
关键字来创建类的实例:
let john = new Person('John', 30);
console.log(john.greet());
在上述代码中,new Person('John', 30)
创建了 Person
类的一个实例,并将其赋值给变量 john
。然后调用 john
的 greet
方法,输出问候语。
二、类的属性修饰符
TypeScript 提供了几种属性修饰符,用于控制类属性的访问权限和行为。
2.1 public
修饰符
public
是默认的属性修饰符,如果没有显式指定修饰符,属性默认为 public
。public
属性可以在类的内部和外部访问。
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
和方法 move
。super
关键字用于调用父类的构造函数,确保父类的属性正确初始化。
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
方法首先调用了父类 Shape
的 describe
方法,获取基本描述,然后再添加自己特有的描述信息。
四、类的静态成员
静态成员是属于类本身而不是类的实例的属性和方法。在 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));
在上述代码中,PI
是 MathUtils
类的静态属性,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
是抽象类,包含两个抽象方法 getArea
和 getPerimeter
。抽象方法只有声明,没有实现。Circle
和 Rectangle
类继承自 Shape
类,并实现了抽象方法。
5.2 抽象类的作用
抽象类提供了一种契约,规定了子类必须实现某些方法。这有助于确保子类具有一致的接口,同时也提供了代码的可扩展性和维护性。例如,在图形绘制的应用中,可以定义一个抽象的 Shape
类,然后有各种具体的形状类如 Circle
、Rectangle
等继承自它,每个具体类根据自身特点实现 getArea
和 getPerimeter
方法。
六、类与接口的关系
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
接口继承了 Shape
和 Colorful
接口,同时定义了自己的 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
方法。Circle
和 Rectangle
类都实现了 Drawable
接口。drawShapes
函数接受一个 Drawable
类型的数组,遍历数组并调用每个对象的 draw
方法,不同的类表现出不同的绘制行为,这就是多态的体现。
七、类的高级特性
7.1 存取器(Accessor)
存取器提供了一种更灵活的方式来访问和修改类的属性,通过 get
和 set
关键字实现。
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
。通过 get
和 set
关键字定义了 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
类实现了 Logger
和 Printer
接口,因此它既可以作为 Logger
类型使用,也可以作为 Printer
类型使用,展现出混合类型的特性。
通过深入理解和运用 TypeScript 中类的各种特性,开发人员能够编写出更健壮、可维护和可扩展的前端代码,充分发挥面向对象编程在前端开发中的优势。无论是小型项目还是大型企业级应用,合理使用类和相关特性都能提升代码质量和开发效率。