Skip to content

TypeScript 类

在 TypeScript 中,类(Class)是面向对象编程的核心概念,它提供了一种组织代码的方式,封装了数据和方法。TypeScript 扩展了 JavaScript 的类语法,添加了类型注解、访问修饰符、抽象类等特性。本文将详细介绍 TypeScript 中的类。

1. 类的定义

基本语法

使用 class 关键字定义类。

typescript
class Person {
  // 属性
  name: string;
  age: number;

  // 构造函数
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // 方法
  greet(): string {
    return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
  }
}

// 创建实例
const person = new Person("John", 30);
console.log(person.greet()); // 输出:Hello, my name is John and I am 30 years old.

类的成员

类的成员包括:

  • 属性:存储数据
  • 方法:执行操作
  • 构造函数:初始化实例
  • 访问器:getter 和 setter 方法
  • 静态成员:属于类本身,而非实例

2. 构造函数

构造函数是类中的特殊方法,用于初始化实例。

基本用法

typescript
class Person {
  name: string;
  age: number;

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

const person = new Person("John", 30);
console.log(person.name); // 输出:John
console.log(person.age); // 输出:30

构造函数参数属性

TypeScript 提供了构造函数参数属性的语法,可以在构造函数参数前添加访问修饰符,自动创建和初始化属性。

typescript
class Person {
  // 使用参数属性
  constructor(public name: string, public age: number) {
    // 不需要手动赋值
  }
}

const person = new Person("John", 30);
console.log(person.name); // 输出:John
console.log(person.age); // 输出:30

可选参数

构造函数参数可以是可选的。

typescript
class Person {
  constructor(public name: string, public age?: number) {
    // age 是可选的
  }
}

const person1 = new Person("John", 30);
console.log(person1.age); // 输出:30

const person2 = new Person("Jane");
console.log(person2.age); // 输出:undefined

默认参数

构造函数参数可以有默认值。

typescript
class Person {
  constructor(public name: string, public age: number = 18) {
    // age 默认为 18
  }
}

const person1 = new Person("John", 30);
console.log(person1.age); // 输出:30

const person2 = new Person("Jane");
console.log(person2.age); // 输出:18

3. 访问修饰符

TypeScript 提供了三种访问修饰符:

  • public:公共的,默认值
  • private:私有的,只能在类内部访问
  • protected:受保护的,只能在类内部和子类中访问

基本用法

typescript
class Person {
  public name: string;      // 公共属性
  private age: number;      // 私有属性
  protected gender: string;  // 受保护属性

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

  public getAge(): number {
    return this.age; // 可以在类内部访问私有属性
  }

  protected getGender(): string {
    return this.gender; // 可以在类内部访问受保护属性
  }
}

const person = new Person("John", 30, "male");
console.log(person.name); // 输出:John
// console.log(person.age); // 错误:属性 "age" 是私有的
// console.log(person.gender); // 错误:属性 "gender" 是受保护的
console.log(person.getAge()); // 输出:30

子类访问

typescript
class Person {
  public name: string;
  private age: number;
  protected gender: string;

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

  private getAge(): number {
    return this.age;
  }

  protected getGender(): string {
    return this.gender;
  }
}

class Employee extends Person {
  constructor(name: string, age: number, gender: string, public employeeId: number) {
    super(name, age, gender);
  }

  getEmployeeInfo(): string {
    // return `Name: ${this.name}, Age: ${this.age}`; // 错误:属性 "age" 是私有的
    return `Name: ${this.name}, Gender: ${this.getGender()}, ID: ${this.employeeId}`;
  }
}

const employee = new Employee("John", 30, "male", 1001);
console.log(employee.getEmployeeInfo()); // 输出:Name: John, Gender: male, ID: 1001

4. 继承

类可以继承其他类,使用 extends 关键字。

基本继承

typescript
class Person {
  constructor(public name: string, public age: number) {}

  greet(): string {
    return `Hello, my name is ${this.name}.`;
  }
}

class Employee extends Person {
  constructor(name: string, age: number, public employeeId: number) {
    super(name, age); // 调用父类构造函数
  }

  // 重写父类方法
  greet(): string {
    return `${super.greet()} I am an employee with ID ${this.employeeId}.`;
  }
}

const employee = new Employee("John", 30, 1001);
console.log(employee.greet()); // 输出:Hello, my name is John. I am an employee with ID 1001.

方法重写

子类可以重写父类的方法,使用 super 关键字调用父类的方法。

typescript
class Animal {
  makeSound(): void {
    console.log("Some generic sound");
  }
}

class Dog extends Animal {
  makeSound(): void {
    super.makeSound(); // 调用父类方法
    console.log("Woof! Woof!");
  }
}

class Cat extends Animal {
  makeSound(): void {
    console.log("Meow! Meow!");
  }
}

const dog = new Dog();
dog.makeSound();
// 输出:
// Some generic sound
// Woof! Woof!

const cat = new Cat();
cat.makeSound();
// 输出:
// Meow! Meow!

5. 抽象类

抽象类是不能直接实例化的类,用于定义子类的共同接口。使用 abstract 关键字定义抽象类。

基本用法

typescript
abstract class Animal {
  constructor(public name: string) {}

  // 抽象方法,子类必须实现
  abstract makeSound(): void;

  // 普通方法
  move(): void {
    console.log(`${this.name} is moving`);
  }
}

class Dog extends Animal {
  makeSound(): void {
    console.log("Woof! Woof!");
  }
}

class Cat extends Animal {
  makeSound(): void {
    console.log("Meow! Meow!");
  }
}

// const animal = new Animal("Generic Animal"); // 错误:不能创建抽象类的实例

const dog = new Dog("Rex");
dog.makeSound(); // 输出:Woof! Woof!
dog.move(); // 输出:Rex is moving

const cat = new Cat("Whiskers");
cat.makeSound(); // 输出:Meow! Meow!
cat.move(); // 输出:Whiskers is moving

抽象属性

抽象类可以定义抽象属性,子类必须实现。

typescript
abstract class Shape {
  // 抽象属性
  abstract name: string;

  // 抽象方法
  abstract calculateArea(): number;

  // 普通方法
  display(): void {
    console.log(`This is a ${this.name}`);
  }
}

class Circle extends Shape {
  name: string = "Circle";

  constructor(private radius: number) {
    super();
  }

  calculateArea(): number {
    return Math.PI * this.radius * this.radius;
  }
}

class Rectangle extends Shape {
  name: string = "Rectangle";

  constructor(private width: number, private height: number) {
    super();
  }

  calculateArea(): number {
    return this.width * this.height;
  }
}

const circle = new Circle(5);
circle.display(); // 输出:This is a Circle
console.log(circle.calculateArea()); // 输出:78.53981633974483

const rectangle = new Rectangle(4, 6);
rectangle.display(); // 输出:This is a Rectangle
console.log(rectangle.calculateArea()); // 输出:24

6. 静态成员

静态成员属于类本身,而非实例,使用 static 关键字定义。

静态属性

typescript
class MathUtils {
  static PI: number = 3.14159;
  static MAX_NUMBER: number = Number.MAX_SAFE_INTEGER;
}

console.log(MathUtils.PI); // 输出:3.14159
console.log(MathUtils.MAX_NUMBER); // 输出:9007199254740991

// const mathUtils = new MathUtils();
// console.log(mathUtils.PI); // 错误:静态属性只能通过类访问

静态方法

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

  static multiply(a: number, b: number): number {
    return a * b;
  }
}

console.log(MathUtils.add(5, 3)); // 输出:8
console.log(MathUtils.multiply(5, 3)); // 输出:15

静态成员的访问

typescript
class Person {
  static species: string = "Homo sapiens";
  name: string;

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

  static getSpecies(): string {
    return Person.species;
  }

  getSpecies(): string {
    return Person.species;
  }
}

console.log(Person.species); // 输出:Homo sapiens
console.log(Person.getSpecies()); // 输出:Homo sapiens

const person = new Person("John");
console.log(person.getSpecies()); // 输出:Homo sapiens
// console.log(person.species); // 错误:静态属性只能通过类访问

7. 访问器

访问器(Accessor)用于控制属性的访问,包括 getter 和 setter 方法。

基本用法

typescript
class Person {
  private _name: string;
  private _age: number;

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

  // getter
  get name(): string {
    return this._name;
  }

  // setter
  set name(value: string) {
    if (value.length > 0) {
      this._name = value;
    }
  }

  // getter
  get age(): number {
    return this._age;
  }

  // setter
  set age(value: number) {
    if (value >= 0) {
      this._age = value;
    }
  }
}

const person = new Person("John", 30);
console.log(person.name); // 输出:John
console.log(person.age); // 输出:30

person.name = "Jane";
person.age = 25;
console.log(person.name); // 输出:Jane
console.log(person.age); // 输出:25

person.name = ""; // 不会改变,因为有验证
person.age = -5; // 不会改变,因为有验证
console.log(person.name); // 输出:Jane
console.log(person.age); // 输出:25

只读访问器

typescript
class Person {
  private _name: string;
  private _age: number;

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

  // 只有 getter,没有 setter,所以是只读的
  get name(): string {
    return this._name;
  }

  get age(): number {
    return this._age;
  }
}

const person = new Person("John", 30);
console.log(person.name); // 输出:John
console.log(person.age); // 输出:30

// person.name = "Jane"; // 错误:没有 setter 方法
// person.age = 25; // 错误:没有 setter 方法

8. 类实现接口

类可以实现接口,使用 implements 关键字。

基本实现

typescript
interface Person {
  name: string;
  age: number;
  greet(): string;
}

class Employee implements Person {
  constructor(public name: string, public age: number, public employeeId: number) {}

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

const employee = new Employee("John", 30, 1001);
console.log(employee.greet()); // 输出:Hello, my name is John and I am 30 years old.

实现多个接口

typescript
interface Person {
  name: string;
  age: number;
}

interface Greetable {
  greet(): string;
}

interface Workable {
  work(): string;
}

class Employee implements Person, Greetable, Workable {
  constructor(public name: string, public age: number, public employeeId: number) {}

  greet(): string {
    return `Hello, my name is ${this.name}.`;
  }

  work(): string {
    return `Employee ${this.employeeId} is working.`;
  }
}

const employee = new Employee("John", 30, 1001);
console.log(employee.greet()); // 输出:Hello, my name is John.
console.log(employee.work()); // 输出:Employee 1001 is working.

9. 类的高级用法

泛型类

类可以使用泛型,使类更加灵活。

typescript
class Container<T> {
  private value: T;

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

  getValue(): T {
    return this.value;
  }

  setValue(value: T): void {
    this.value = value;
  }
}

// 使用数字类型
const numberContainer = new Container<number>(10);
console.log(numberContainer.getValue()); // 输出:10
numberContainer.setValue(20);
console.log(numberContainer.getValue()); // 输出:20

// 使用字符串类型
const stringContainer = new Container<string>("hello");
console.log(stringContainer.getValue()); // 输出:hello
stringContainer.setValue("world");
console.log(stringContainer.getValue()); // 输出:world

// 使用对象类型
interface Person {
  name: string;
  age: number;
}

const personContainer = new Container<Person>({ name: "John", age: 30 });
console.log(personContainer.getValue()); // 输出:{ name: 'John', age: 30 }

类的继承与接口实现结合

typescript
interface Greetable {
  greet(): string;
}

class Person {
  constructor(public name: string, public age: number) {}
}

class Employee extends Person implements Greetable {
  constructor(name: string, age: number, public employeeId: number) {
    super(name, age);
  }

  greet(): string {
    return `Hello, my name is ${this.name} and I am an employee with ID ${this.employeeId}.`;
  }
}

const employee = new Employee("John", 30, 1001);
console.log(employee.greet()); // 输出:Hello, my name is John and I am an employee with ID 1001.

类的类型

类本身也是一种类型,可以用于类型注解。

typescript
class Person {
  constructor(public name: string, public age: number) {}

  greet(): string {
    return `Hello, my name is ${this.name}.`;
  }
}

// 使用类作为类型
function createPerson(person: Person): Person {
  return person;
}

const person = new Person("John", 30);
const newPerson = createPerson(person);
console.log(newPerson.greet()); // 输出:Hello, my name is John.

// 使用类的构造函数作为类型
function createPersonInstance(ctor: new (name: string, age: number) => Person, name: string, age: number): Person {
  return new ctor(name, age);
}

const person2 = createPersonInstance(Person, "Jane", 25);
console.log(person2.greet()); // 输出:Hello, my name is Jane.

10. 类的最佳实践

1. 使用访问修饰符

使用访问修饰符来控制属性和方法的访问权限,提高代码的封装性和安全性。

typescript
// 推荐:使用访问修饰符
class Person {
  constructor(private name: string, private age: number) {}

  public getName(): string {
    return this.name;
  }

  public setName(name: string): void {
    this.name = name;
  }
}

// 不推荐:使用公共属性
class Person {
  constructor(public name: string, public age: number) {}
}

2. 使用构造函数参数属性

使用构造函数参数属性来简化代码。

typescript
// 推荐:使用构造函数参数属性
class Person {
  constructor(private name: string, private age: number) {}
}

// 不推荐:手动赋值
class Person {
  private name: string;
  private age: number;

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

3. 使用抽象类

对于具有共同行为的类,使用抽象类来定义共同接口。

typescript
// 推荐:使用抽象类
abstract class Animal {
  abstract makeSound(): void;

  move(): void {
    console.log("Moving...");
  }
}

class Dog extends Animal {
  makeSound(): void {
    console.log("Woof! Woof!");
  }
}

// 不推荐:重复定义共同方法
class Dog {
  makeSound(): void {
    console.log("Woof! Woof!");
  }

  move(): void {
    console.log("Moving...");
  }
}

class Cat {
  makeSound(): void {
    console.log("Meow! Meow!");
  }

  move(): void {
    console.log("Moving...");
  }
}

4. 使用访问器

对于需要验证或计算的属性,使用访问器。

typescript
// 推荐:使用访问器
class Person {
  private _age: number;

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

  get age(): number {
    return this._age;
  }

  set age(value: number) {
    if (value >= 0) {
      this._age = value;
    }
  }
}

// 不推荐:直接访问属性
class Person {
  constructor(private name: string, public age: number) {}
}

5. 合理使用静态成员

对于与类相关但与实例无关的属性和方法,使用静态成员。

typescript
// 推荐:使用静态成员
class MathUtils {
  static PI: number = 3.14159;

  static calculateArea(radius: number): number {
    return this.PI * radius * radius;
  }
}

// 不推荐:使用实例成员
class MathUtils {
  PI: number = 3.14159;

  calculateArea(radius: number): number {
    return this.PI * radius * radius;
  }
}

11. 常见错误

1. 忘记调用父类构造函数

在子类中,如果定义了构造函数,必须调用父类的构造函数。

typescript
class Person {
  constructor(public name: string) {}
}

// 错误:忘记调用父类构造函数
// class Employee extends Person {
//   constructor(name: string, public employeeId: number) {
//     // 缺少 super(name);
//   }
// }

// 正确
class Employee extends Person {
  constructor(name: string, public employeeId: number) {
    super(name); // 调用父类构造函数
  }
}

2. 访问修饰符使用错误

typescript
class Person {
  private name: string;

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

const person = new Person("John");
// console.log(person.name); // 错误:属性 "name" 是私有的

3. 抽象类实例化

抽象类不能直接实例化。

typescript
abstract class Animal {
  abstract makeSound(): void;
}

// 错误:不能创建抽象类的实例
// const animal = new Animal();

// 正确:创建子类实例
class Dog extends Animal {
  makeSound(): void {
    console.log("Woof! Woof!");
  }
}

const dog = new Dog();
dog.makeSound();

4. 方法重写错误

子类重写父类方法时,方法签名必须匹配。

typescript
class Person {
  greet(name: string): string {
    return `Hello, ${name}!`;
  }
}

// 错误:方法签名不匹配
// class Employee extends Person {
//   greet(): string { // 缺少参数
//     return "Hello!";
//   }
// }

// 正确
class Employee extends Person {
  greet(name: string): string {
    return `Hello, ${name}! I am an employee.`;
  }
}

5. 静态成员访问错误

静态成员只能通过类访问,不能通过实例访问。

typescript
class MathUtils {
  static PI: number = 3.14159;
}

// 错误:静态成员只能通过类访问
// const mathUtils = new MathUtils();
// console.log(mathUtils.PI);

// 正确
console.log(MathUtils.PI);

12. 实际应用场景

1. 定义模型类

typescript
class User {
  constructor(
    public id: number,
    public name: string,
    public email: string,
    private password: string
  ) {}

  getPassword(): string {
    return this.password;
  }

  setPassword(password: string): void {
    this.password = password;
  }
}

const user = new User(1, "John", "john@example.com", "password123");
console.log(user.name); // 输出:John
console.log(user.email); // 输出:john@example.com
// console.log(user.password); // 错误:属性 "password" 是私有的
console.log(user.getPassword()); // 输出:password123

2. 实现业务逻辑

typescript
class ShoppingCart {
  private items: { product: string; price: number; quantity: number }[] = [];

  addItem(product: string, price: number, quantity: number): void {
    this.items.push({ product, price, quantity });
  }

  removeItem(product: string): void {
    this.items = this.items.filter(item => item.product !== product);
  }

  calculateTotal(): number {
    return this.items.reduce((total, item) => total + item.price * item.quantity, 0);
  }

  getItems(): { product: string; price: number; quantity: number }[] {
    return this.items;
  }
}

const cart = new ShoppingCart();
cart.addItem("Apple", 1.99, 3);
cart.addItem("Banana", 0.99, 5);
console.log(cart.getItems()); // 输出购物车 items
console.log(cart.calculateTotal()); // 输出:10.92
cart.removeItem("Apple");
console.log(cart.getItems()); // 输出购物车 items
console.log(cart.calculateTotal()); // 输出:4.95

3. 实现设计模式

单例模式

typescript
class Singleton {
  private static instance: Singleton;

  private constructor() {
    // 私有构造函数,防止外部实例化
  }

  static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }

  doSomething(): void {
    console.log("Doing something...");
  }
}

// const singleton = new Singleton(); // 错误:构造函数是私有的

const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();

console.log(singleton1 === singleton2); // 输出:true
singleton1.doSomething(); // 输出:Doing something...

工厂模式

typescript
interface Shape {
  calculateArea(): number;
}

class Circle implements Shape {
  constructor(private radius: number) {}

  calculateArea(): number {
    return Math.PI * this.radius * this.radius;
  }
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}

  calculateArea(): number {
    return this.width * this.height;
  }
}

class ShapeFactory {
  static createShape(type: string, ...args: any[]): Shape {
    switch (type) {
      case "circle":
        return new Circle(args[0]);
      case "rectangle":
        return new Rectangle(args[0], args[1]);
      default:
        throw new Error(`Unknown shape type: ${type}`);
    }
  }
}

const circle = ShapeFactory.createShape("circle", 5);
console.log(circle.calculateArea()); // 输出:78.53981633974483

const rectangle = ShapeFactory.createShape("rectangle", 4, 6);
console.log(rectangle.calculateArea()); // 输出:24

4. 实现服务类

typescript
class ApiService {
  private baseUrl: string;

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

  async fetchData(endpoint: string): Promise<any> {
    const response = await fetch(`${this.baseUrl}/${endpoint}`);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  }

  async postData(endpoint: string, data: any): Promise<any> {
    const response = await fetch(`${this.baseUrl}/${endpoint}`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  }
}

const apiService = new ApiService("https://api.example.com");

// 使用
apiService.fetchData("users").then(data => {
  console.log(data);
}).catch(error => {
  console.error(error);
});

apiService.postData("users", { name: "John", email: "john@example.com" }).then(data => {
  console.log(data);
}).catch(error => {
  console.error(error);
});

5. 实现工具类

typescript
class DateUtils {
  static formatDate(date: Date): string {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
  }

  static parseDate(dateString: string): Date {
    return new Date(dateString);
  }

  static getDaysBetween(date1: Date, date2: Date): number {
    const timeDiff = Math.abs(date2.getTime() - date1.getTime());
    return Math.ceil(timeDiff / (1000 * 3600 * 24));
  }
}

const today = new Date();
console.log(DateUtils.formatDate(today)); // 输出:2023-10-05(示例)

const dateString = "2023-10-01";
const parsedDate = DateUtils.parseDate(dateString);
console.log(parsedDate); // 输出:2023-10-01T00:00:00.000Z

const date1 = new Date("2023-10-01");
const date2 = new Date("2023-10-05");
console.log(DateUtils.getDaysBetween(date1, date2)); // 输出:4

总结

TypeScript 中的类是面向对象编程的核心概念,它提供了一种组织代码的方式,封装了数据和方法。TypeScript 扩展了 JavaScript 的类语法,添加了类型注解、访问修饰符、抽象类等特性。本文介绍了 TypeScript 类的以下内容:

  • 类的定义:基本语法,类的成员
  • 构造函数:基本用法,构造函数参数属性,可选参数,默认参数
  • 访问修饰符:public,private,protected
  • 继承:基本继承,方法重写
  • 抽象类:基本用法,抽象属性
  • 静态成员:静态属性,静态方法,静态成员的访问
  • 访问器:getter 和 setter 方法,只读访问器
  • 类实现接口:基本实现,实现多个接口
  • 类的高级用法:泛型类,类的继承与接口实现结合,类的类型
  • 最佳实践:使用访问修饰符,使用构造函数参数属性,使用抽象类,使用访问器,合理使用静态成员
  • 常见错误:忘记调用父类构造函数,访问修饰符使用错误,抽象类实例化,方法重写错误,静态成员访问错误
  • 实际应用场景:定义模型类,实现业务逻辑,实现设计模式(单例模式、工厂模式),实现服务类,实现工具类

通过合理使用类,你可以在 TypeScript 中构建更加模块化、可维护的代码,提高代码的可读性和可重用性。