TypeScript 访问修饰符作用域详解

139 阅读2分钟

TypeScript 访问修饰符作用域详解

访问修饰符概览

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

  • public(默认)
  • private
  • protected

详细说明

1. public 修饰符

作用域
  • 类内部可以访问
  • 类的实例可以访问
  • 子类可以访问
  • 类外部可以访问
示例
class Animal {
  public name: string;
  
  constructor(name: string) {
    this.name = name;  // 类内部访问
  }
}

class Dog extends Animal {
  bark() {
    console.log(this.name);  // 子类访问
  }
}

const animal = new Animal("Pet");
console.log(animal.name);    // 外部访问

2. private 修饰符

作用域
  • 只能在声明的类内部访问
  • 类的实例不能访问
  • 子类不能访问
  • 类外部不能访问
示例
class BankAccount {
  private balance: number = 0;
  
  constructor(initialBalance: number) {
    this.balance = initialBalance;  // 类内部访问 OK
  }
  
  private updateBalance(amount: number) {
    this.balance += amount;
  }
}

class ChildAccount extends BankAccount {
  checkBalance() {
    // this.balance;       // 错误:子类不能访问
    // this.updateBalance; // 错误:子类不能访问
  }
}

const account = new BankAccount(100);
// account.balance;        // 错误:实例不能访问
// account.updateBalance;  // 错误:实例不能访问

3. protected 修饰符

作用域
  • 类内部可以访问
  • 子类可以访问
  • 类的实例不能访问
  • 类外部不能访问
示例
class Person {
  protected age: number;
  
  constructor(age: number) {
    this.age = age;      // 类内部访问 OK
  }
  
  protected getAge() {
    return this.age;
  }
}

class Employee extends Person {
  getEmployeeAge() {
    return this.age;     // 子类访问 OK
    this.getAge();       // 子类访问 OK
  }
}

const person = new Person(30);
// person.age;           // 错误:实例不能访问
// person.getAge();      // 错误:实例不能访问

访问修饰符对比表

访问位置publicprivateprotected
类内部✅ 可以✅ 可以✅ 可以
子类✅ 可以❌ 不可以✅ 可以
实例✅ 可以❌ 不可以❌ 不可以
类外部✅ 可以❌ 不可以❌ 不可以

实际应用场景

1. 封装私有实现

class UserService {
  private apiClient: ApiClient;
  
  constructor() {
    this.apiClient = new ApiClient();  // 私有实现细节
  }
  
  public async getUser(id: string) {
    // 公共 API
    return this.apiClient.get(`/users/${id}`);
  }
}

2. 保护基类方法

abstract class Database {
  protected abstract connect(): void;
  protected abstract disconnect(): void;
  
  public query(sql: string) {
    this.connect();
    // 执行查询
    this.disconnect();
  }
}

class MySQLDatabase extends Database {
  protected connect() {
    // MySQL 特定的连接逻辑
  }
  
  protected disconnect() {
    // MySQL 特定的断开逻辑
  }
}

3. 私有状态管理

class Counter {
  private count: number = 0;
  
  public increment() {
    this.count++;
    this.notifyUpdate();
  }
  
  public decrement() {
    this.count--;
    this.notifyUpdate();
  }
  
  private notifyUpdate() {
    console.log(`Count updated: ${this.count}`);
  }
  
  public getCount() {
    return this.count;
  }
}

最佳实践

  1. 默认使用 private

    • 除非确实需要在外部访问,否则优先使用 private
    • 有助于减少代码耦合
  2. 谨慎使用 protected

    • 只在确实需要子类访问时使用
    • 避免过度暴露内部细节
  3. 使用 public 接口

    • 为外部提供清晰的公共 API
    • 隐藏实现细节
  4. 封装变化

    • 使用私有成员封装可能变化的实现
    • 通过公共方法提供稳定的接口

注意事项

  1. JavaScript 运行时

    • TypeScript 的访问修饰符在编译后会被移除
    • 不能依赖访问修饰符来实现运行时的安全性
  2. 私有字段 vs private

    • ES2019+ 提供了 # 私有字段语法
    • 与 TypeScript 的 private 修饰符不同,# 私有字段在运行时也有效
  3. 构造函数参数

class Person {
  constructor(
    public name: string,    // 自动创建公共属性
    private age: number,    // 自动创建私有属性
    protected id: string    // 自动创建受保护属性
  ) {}
}