Skip to content

类基础

概述

类(Class)是 TypeScript 中面向对象编程的核心概念。类提供了一种创建对象的模板,定义了对象的属性和方法。通过类,我们可以封装数据和行为,实现代码的复用和组织。TypeScript 的类支持 ES6+ 的类语法,并在此基础上添加了类型注解、访问修饰符等增强特性,使得类的使用更加安全和灵活。

基本语法

类定义

使用 class 关键字定义类,类名通常使用 PascalCase(首字母大写的驼峰命名):

typescript
// 基本类定义
class Person {
  name: string;
  age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  
  greet(): void {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

// 创建类的实例
const person = new Person("Alice", 30);
person.greet(); // "Hello, my name is Alice and I am 30 years old."

构造函数

构造函数(Constructor)是类的特殊方法,用于初始化类的实例。构造函数使用 constructor 关键字定义,在创建类的实例时自动调用:

typescript
class User {
  name: string;
  email: string;
  
  // 构造函数
  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
  }
}

// 使用 new 关键字创建实例
const user = new User("John", "john@example.com");
console.log(user.name);  // "John"
console.log(user.email); // "john@example.com"

提示

每个类只能有一个构造函数。如果不定义构造函数,TypeScript 会提供一个默认的无参构造函数。

实例属性

实例属性是类的成员变量,每个类的实例都有自己独立的属性值:

typescript
class Product {
  // 声明实例属性
  name: string;
  price: number;
  inStock: boolean;
  
  constructor(name: string, price: number, inStock: boolean = true) {
    // 在构造函数中初始化属性
    this.name = name;
    this.price = price;
    this.inStock = inStock;
  }
}

// 创建实例
const product1 = new Product("Laptop", 999, true);
const product2 = new Product("Mouse", 29, false);

// 每个实例有独立的属性值
console.log(product1.name);  // "Laptop"
console.log(product2.name);  // "Mouse"

方法

方法是定义在类中的函数,用于定义对象的行为:

typescript
class Calculator {
  // 实例方法
  add(a: number, b: number): number {
    return a + b;
  }
  
  subtract(a: number, b: number): number {
    return a - b;
  }
  
  multiply(a: number, b: number): number {
    return a * b;
  }
  
  divide(a: number, b: number): number {
    if (b === 0) {
      throw new Error("Division by zero is not allowed");
    }
    return a / b;
  }
}

// 使用实例方法
const calc = new Calculator();
console.log(calc.add(5, 3));        // 8
console.log(calc.subtract(10, 4));  // 6
console.log(calc.multiply(3, 4));   // 12
console.log(calc.divide(15, 3));    // 5

属性初始化

在构造函数中初始化

最常见的属性初始化方式是在构造函数中赋值:

typescript
class Student {
  name: string;
  age: number;
  grade: string;
  
  constructor(name: string, age: number, grade: string) {
    this.name = name;
    this.age = age;
    this.grade = grade;
  }
}

const student = new Student("Alice", 18, "A");

属性初始化器

TypeScript 支持在声明属性时直接初始化(属性初始化器):

typescript
class Config {
  // 使用属性初始化器
  apiUrl: string = "https://api.example.com";
  timeout: number = 5000;
  retries: number = 3;
  
  // 也可以在构造函数中重新赋值
  constructor(apiUrl?: string) {
    if (apiUrl) {
      this.apiUrl = apiUrl;
    }
  }
}

const config = new Config();
console.log(config.apiUrl);  // "https://api.example.com"
console.log(config.timeout); // 5000

const customConfig = new Config("https://custom.api.com");
console.log(customConfig.apiUrl); // "https://custom.api.com"

可选属性

属性可以声明为可选,使用 ? 标记:

typescript
class User {
  name: string;
  email: string;
  phone?: string;  // 可选属性
  
  constructor(name: string, email: string, phone?: string) {
    this.name = name;
    this.email = email;
    this.phone = phone;  // 可选参数
  }
  
  getContactInfo(): string {
    // 访问可选属性时需要检查
    return this.phone 
      ? `${this.name} - ${this.email} - ${this.phone}`
      : `${this.name} - ${this.email}`;
  }
}

const user1 = new User("Alice", "alice@example.com");
const user2 = new User("Bob", "bob@example.com", "123-456-7890");

console.log(user1.getContactInfo()); // "Alice - alice@example.com"
console.log(user2.getContactInfo()); // "Bob - bob@example.com - 123-456-7890"

访问修饰符

TypeScript 提供了访问修饰符来控制类成员的可见性:

public(默认)

public 是默认的访问修饰符,成员可以在任何地方访问:

typescript
class Person {
  // public 是默认的,可以省略
  public name: string;
  public age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  
  public greet(): void {
    console.log(`Hello, I'm ${this.name}`);
  }
}

const person = new Person("Alice", 30);
console.log(person.name);  // 可以访问
person.greet();            // 可以调用

private

private 成员只能在类内部访问:

typescript
class BankAccount {
  private balance: number;  // 私有属性
  
  constructor(initialBalance: number) {
    this.balance = initialBalance;
  }
  
  // 公共方法访问私有属性
  public deposit(amount: number): void {
    if (amount > 0) {
      this.balance += amount;
    }
  }
  
  public withdraw(amount: number): boolean {
    if (amount > 0 && amount <= this.balance) {
      this.balance -= amount;
      return true;
    }
    return false;
  }
  
  public getBalance(): number {
    return this.balance;
  }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500

// ❌ 错误:不能直接访问私有属性
// console.log(account.balance); // Error: Property 'balance' is private

protected

protected 成员可以在类内部和子类中访问:

typescript
class Animal {
  protected name: string;  // 受保护属性
  
  constructor(name: string) {
    this.name = name;
  }
  
  protected makeSound(): void {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  // 子类可以访问 protected 成员
  public bark(): void {
    this.makeSound();  // 可以访问父类的 protected 方法
    console.log(`${this.name} barks: Woof!`);  // 可以访问父类的 protected 属性
  }
}

const dog = new Dog("Buddy");
dog.bark(); // "Buddy makes a sound" "Buddy barks: Woof!"

// ❌ 错误:外部不能访问 protected 成员
// dog.makeSound(); // Error: Property 'makeSound' is protected
// console.log(dog.name); // Error: Property 'name' is protected

信息

关于访问修饰符的详细内容,请参考访问修饰符章节。

只读属性

使用 readonly 关键字可以将属性标记为只读,只读属性只能在声明时或构造函数中初始化:

typescript
class User {
  readonly id: number;
  name: string;
  readonly createdAt: Date;
  
  constructor(id: number, name: string) {
    this.id = id;  // 可以在构造函数中初始化
    this.name = name;
    this.createdAt = new Date();  // 可以在构造函数中初始化
  }
  
  updateName(newName: string): void {
    this.name = newName;  // ✅ 可以修改普通属性
    
    // ❌ 错误:不能修改只读属性
    // this.id = 999; // Error: Cannot assign to 'id' because it is a read-only property
    // this.createdAt = new Date(); // Error
  }
}

const user = new User(1, "Alice");
console.log(user.id);  // 1
console.log(user.createdAt);  // 当前日期

// ❌ 错误:不能修改只读属性
// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property

注意

readonly 只在编译时生效,运行时仍然可以修改。如果需要真正的不可变对象,可以使用 Object.freeze() 或第三方库。

使用示例

示例 1:图书管理系统

typescript
class Book {
  private id: number;
  public title: string;
  public author: string;
  public isbn: string;
  private available: boolean;
  readonly createdAt: Date;
  
  constructor(id: number, title: string, author: string, isbn: string) {
    this.id = id;
    this.title = title;
    this.author = author;
    this.isbn = isbn;
    this.available = true;
    this.createdAt = new Date();
  }
  
  // 公共方法访问私有属性
  public borrow(): boolean {
    if (this.available) {
      this.available = false;
      return true;
    }
    return false;
  }
  
  public return(): void {
    this.available = true;
  }
  
  public isAvailable(): boolean {
    return this.available;
  }
  
  public getInfo(): string {
    return `${this.title} by ${this.author} (ISBN: ${this.isbn})`;
  }
}

// 使用示例
const book1 = new Book(1, "TypeScript Handbook", "Microsoft", "978-0-123456-78-9");
const book2 = new Book(2, "JavaScript Guide", "Mozilla", "978-0-987654-32-1");

console.log(book1.getInfo()); // "TypeScript Handbook by Microsoft (ISBN: 978-0-123456-78-9)"

if (book1.isAvailable()) {
  book1.borrow();
  console.log("Book borrowed successfully");
}

book1.return();
console.log(book1.isAvailable()); // true

示例 2:购物车系统

typescript
class CartItem {
  public productId: number;
  public productName: string;
  public price: number;
  private quantity: number;
  
  constructor(productId: number, productName: string, price: number, quantity: number = 1) {
    this.productId = productId;
    this.productName = productName;
    this.price = price;
    this.quantity = quantity;
  }
  
  public increaseQuantity(amount: number = 1): void {
    if (amount > 0) {
      this.quantity += amount;
    }
  }
  
  public decreaseQuantity(amount: number = 1): void {
    if (amount > 0 && this.quantity >= amount) {
      this.quantity -= amount;
    }
  }
  
  public getQuantity(): number {
    return this.quantity;
  }
  
  public getTotalPrice(): number {
    return this.price * this.quantity;
  }
}

class ShoppingCart {
  private items: CartItem[] = [];
  
  public addItem(item: CartItem): void {
    this.items.push(item);
  }
  
  public removeItem(productId: number): boolean {
    const index = this.items.findIndex(item => item.productId === productId);
    if (index > -1) {
      this.items.splice(index, 1);
      return true;
    }
    return false;
  }
  
  public getTotal(): number {
    return this.items.reduce((total, item) => total + item.getTotalPrice(), 0);
  }
  
  public getItemCount(): number {
    return this.items.length;
  }
  
  public clear(): void {
    this.items = [];
  }
}

// 使用示例
const cart = new ShoppingCart();

const item1 = new CartItem(1, "Laptop", 999, 1);
const item2 = new CartItem(2, "Mouse", 29, 2);

cart.addItem(item1);
cart.addItem(item2);

console.log(`Cart total: $${cart.getTotal()}`); // "Cart total: $1057"
console.log(`Items in cart: ${cart.getItemCount()}`); // "Items in cart: 2"

item2.increaseQuantity(1);
console.log(`Updated total: $${cart.getTotal()}`); // "Updated total: $1086"

示例 3:用户认证系统

typescript
class User {
  private id: number;
  public username: string;
  private password: string;  // 私有属性,不应该直接访问
  public email: string;
  private isActive: boolean;
  readonly registeredAt: Date;
  
  constructor(id: number, username: string, password: string, email: string) {
    this.id = id;
    this.username = username;
    this.password = password;  // 实际应用中应该加密
    this.email = email;
    this.isActive = true;
    this.registeredAt = new Date();
  }
  
  // 验证密码(不直接暴露密码)
  public verifyPassword(inputPassword: string): boolean {
    return this.password === inputPassword;  // 实际应用中应该使用哈希比较
  }
  
  // 更改密码
  public changePassword(oldPassword: string, newPassword: string): boolean {
    if (this.verifyPassword(oldPassword)) {
      this.password = newPassword;  // 实际应用中应该加密
      return true;
    }
    return false;
  }
  
  // 激活/停用账户
  public activate(): void {
    this.isActive = true;
  }
  
  public deactivate(): void {
    this.isActive = false;
  }
  
  public getStatus(): string {
    return this.isActive ? "Active" : "Inactive";
  }
  
  public getInfo(): string {
    return `User: ${this.username} (${this.email}) - Status: ${this.getStatus()}`;
  }
}

// 使用示例
const user = new User(1, "alice", "password123", "alice@example.com");
console.log(user.getInfo()); // "User: alice (alice@example.com) - Status: Active"

// 验证密码
if (user.verifyPassword("password123")) {
  console.log("Password correct");
}

// 更改密码
if (user.changePassword("password123", "newPassword456")) {
  console.log("Password changed successfully");
}

// ❌ 错误:不能直接访问私有属性
// console.log(user.password); // Error: Property 'password' is private
// console.log(user.id); // Error: Property 'id' is private

示例 4:任务管理系统

typescript
enum TaskStatus {
  Pending = "pending",
  InProgress = "in-progress",
  Completed = "completed",
  Cancelled = "cancelled"
}

class Task {
  private id: number;
  public title: string;
  public description: string;
  private status: TaskStatus;
  public priority: "low" | "medium" | "high";
  readonly createdAt: Date;
  private completedAt?: Date;
  
  constructor(
    id: number,
    title: string,
    description: string,
    priority: "low" | "medium" | "high" = "medium"
  ) {
    this.id = id;
    this.title = title;
    this.description = description;
    this.status = TaskStatus.Pending;
    this.priority = priority;
    this.createdAt = new Date();
  }
  
  public start(): void {
    if (this.status === TaskStatus.Pending) {
      this.status = TaskStatus.InProgress;
    }
  }
  
  public complete(): void {
    if (this.status === TaskStatus.InProgress) {
      this.status = TaskStatus.Completed;
      this.completedAt = new Date();
    }
  }
  
  public cancel(): void {
    if (this.status !== TaskStatus.Completed) {
      this.status = TaskStatus.Cancelled;
    }
  }
  
  public getStatus(): TaskStatus {
    return this.status;
  }
  
  public getInfo(): string {
    return `[${this.status.toUpperCase()}] ${this.title} (Priority: ${this.priority})`;
  }
  
  public getDuration(): string | null {
    if (this.completedAt) {
      const duration = this.completedAt.getTime() - this.createdAt.getTime();
      const hours = Math.floor(duration / (1000 * 60 * 60));
      const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60));
      return `${hours}h ${minutes}m`;
    }
    return null;
  }
}

// 使用示例
const task1 = new Task(1, "Learn TypeScript", "Complete TypeScript tutorial", "high");
const task2 = new Task(2, "Write documentation", "Document the API", "medium");

console.log(task1.getInfo()); // "[PENDING] Learn TypeScript (Priority: high)"

task1.start();
console.log(task1.getStatus()); // TaskStatus.InProgress

task1.complete();
console.log(task1.getInfo()); // "[COMPLETED] Learn TypeScript (Priority: high)"
console.log(task1.getDuration()); // "0h 0m" (或实际耗时)

类型检查示例

常见错误

typescript
// ❌ 错误:缺少必需的属性
class Person {
  name: string;
  age: number;
}

const person = new Person();
// Error: Expected 0 arguments, but got 0.
// 如果定义了构造函数参数,必须提供参数

// ❌ 错误:类型不匹配
class User {
  name: string;
  age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

const user = new User("Alice", "30");
// Error: Argument of type 'string' is not assignable to parameter of type 'number'

// ❌ 错误:访问私有成员
class BankAccount {
  private balance: number;
  
  constructor(balance: number) {
    this.balance = balance;
  }
}

const account = new BankAccount(1000);
console.log(account.balance);
// Error: Property 'balance' is private and only accessible within class 'BankAccount'

// ❌ 错误:修改只读属性
class Config {
  readonly apiKey: string;
  
  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }
}

const config = new Config("abc123");
config.apiKey = "new-key";
// Error: Cannot assign to 'apiKey' because it is a read-only property

正确写法

typescript
// ✅ 正确:提供所有必需的构造函数参数
class Person {
  name: string;
  age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

const person = new Person("Alice", 30);

// ✅ 正确:类型匹配
class User {
  name: string;
  age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

const user = new User("Alice", 30);

// ✅ 正确:通过公共方法访问私有成员
class BankAccount {
  private balance: number;
  
  constructor(balance: number) {
    this.balance = balance;
  }
  
  public getBalance(): number {
    return this.balance;
  }
}

const account = new BankAccount(1000);
console.log(account.getBalance()); // 1000

// ✅ 正确:只读属性在构造函数中初始化
class Config {
  readonly apiKey: string;
  
  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }
}

const config = new Config("abc123");
console.log(config.apiKey); // "abc123"

注意事项

提示

  • 类名使用 PascalCase 命名(首字母大写的驼峰命名)
  • 使用构造函数初始化实例属性
  • 合理使用访问修饰符(public、private、protected)来控制成员的可见性
  • 只读属性(readonly)适合用于不会改变的值,如 ID、创建时间等
  • 私有属性通常用于封装内部状态,通过公共方法提供访问接口

注意

  • 如果不定义构造函数,TypeScript 会提供一个默认的无参构造函数
  • 访问修饰符只在编译时生效,运行时仍然可以访问所有成员
  • readonly 只在编译时生效,运行时仍然可以修改(需要使用 Object.freeze() 实现真正的不可变)
  • 私有成员(private)在编译后的 JavaScript 中仍然可以访问,TypeScript 的访问控制只在编译时检查
  • 类的实例属性必须明确初始化,要么在声明时,要么在构造函数中

重要

  • 类是 TypeScript 面向对象编程的基础,理解类对于学习继承、抽象类等高级概念非常重要
  • 合理使用访问修饰符可以提高代码的封装性和安全性
  • 类的类型检查发生在编译时,不会影响运行时的性能
  • 类与接口结合使用,可以实现强大的类型系统

信息

类的优势

  • 封装:将数据和行为组织在一起
  • 复用:通过继承实现代码复用
  • 类型安全:编译时类型检查
  • 组织代码:更好的代码组织结构

类的应用场景

  • 领域模型(User、Product、Order 等)
  • 业务逻辑封装
  • 数据访问层
  • 服务类
  • 工具类

相关链接

基于 VitePress 构建