TypeScript类:从基础到高级的全面指南
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
类有两个属性 name
和 age
,类型分别为 string
和 number
。constructor
是类的构造函数,用于初始化对象的属性。greet
是一个实例方法,用于返回问候语。
创建类的实例
定义好类后,可以通过 new
关键字创建类的实例:
let john = new Person('John', 30);
console.log(john.greet());
这里创建了 Person
类的一个实例 john
,并调用了 greet
方法。
访问修饰符
TypeScript 支持三种访问修饰符:public
、private
和 protected
。
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
类内部访问 brand
和 describe
。
类的继承
继承是面向对象编程的重要特性之一,允许一个类继承另一个类的属性和方法。
基本继承
通过 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));
在这个例子中,PI
是 MathUtils
类的静态属性,calculateCircleArea
是静态方法。可以直接通过类名来访问静态属性和方法,而不需要创建类的实例。
静态方法
静态方法通常用于执行与类相关但不依赖于特定实例的操作。例如,工具类中的一些通用计算方法。
class StringUtils {
static capitalizeFirstLetter(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
}
let result = StringUtils.capitalizeFirstLetter('hello');
console.log(result);
这里 capitalizeFirstLetter
是 StringUtils
类的静态方法,用于将字符串的首字母大写。
抽象类
抽象类是一种不能被实例化的类,它主要用于定义一些抽象方法,让子类去实现这些方法。通过 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
是抽象类,包含两个抽象方法 getArea
和 getPerimeter
。Rectangle
类继承自 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
类实现了 Printable
和 Resizable
两个接口,必须实现这两个接口定义的所有方法。
类的高级特性
类的类型别名
可以使用类型别名来为类定义一个更简洁的类型表示。
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);
这里 PersonType
是 Person
类的类型别名,使用 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
实例时指定具体的类型,如 number
或 string
。
装饰器
装饰器是一种在类的声明及成员上添加标注的方式,用于修改类的行为或添加额外的功能。在 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
函数将 A
和 B
类的方法混入到 C
类中,使得 C
类可以使用 A
和 B
类的方法。
类在前端开发中的应用场景
组件化开发
在前端框架如 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
,定义了 props
和 state
的类型,并实现了组件的渲染逻辑和状态更新方法。
数据模型封装
在处理复杂的数据逻辑时,可以使用类来封装数据模型及其相关操作。例如,在一个电商应用中,可以定义一个 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);
在这个例子中,StringUtils
和 MathUtils
类封装在 utils.ts
模块中,在 main.ts
中通过 import
引入并使用。
总结
TypeScript 的类提供了丰富的面向对象编程特性,从基础的类定义、属性和方法,到高级的继承、静态成员、抽象类、接口实现、泛型类、装饰器和混入等。这些特性使得代码更加结构化、可维护和可扩展,在前端开发中有着广泛的应用场景,如组件化开发、数据模型封装和模块管理等。通过合理运用 TypeScript 类的各种特性,可以提高前端代码的质量和开发效率。