首页 / TypeScript 入门教程 / 访问修饰符

TypeScript 入门教程

访问修饰符

本教程共 80 篇 · 第 31 篇 · 更新于 2026-08-10 · 约 11 分钟阅读

TypeScriptTypeScript 入门教程访问修饰符privateprotected

本节目标:掌握 public、private、protected 三种访问修饰符,理解 TypeScript private 与 JavaScript # 的区别,知道什么时候该用哪个。

面向对象编程的三大支柱之一是封装——把内部实现藏起来,只暴露必要的接口。打个比方:你开车时踩油门、转方向盘就够了,不需要知道发动机怎么点火、变速箱怎么换挡。

TypeScript 提供了三种访问修饰符来实现封装。

public:默认开放

public 是默认修饰符。不加任何修饰符的成员就是 public,类内部、子类、类外部都能访问:

class Animal {
  name: string;          // 默认 public

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

  speak(): void {        // 默认 public
    console.log(`${this.name} 发出声音`);
  }
}

const a = new Animal("小猫");
console.log(a.name);     // ✅ 外部可以访问
a.speak();               // ✅ 外部可以调用

不需要public,但有时为了代码可读性,团队可能会统一要求写上。写不写纯属风格偏好,不影响类型检查。

private:类内部专用

private 成员只能在定义它的类内部访问,外部和子类都碰不到:

class BankAccount {
  private balance: number;

  constructor(initialBalance: number) {
    this.balance = initialBalance;
  }

  deposit(amount: number): void {
    if (amount > 0) {
      this.balance += amount;   // ✅ 类内部可以访问
    }
  }

  getBalance(): number {
    return this.balance;         // ✅ 类内部可以访问
  }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// console.log(account.balance);   // ❌ 外部不能访问

子类也不能访问 private 成员:

class Animal {
  private dna = "ACGT";

  protected getDNA(): string {
    return this.dna;  // ✅ 自己内部可以访问
  }
}

class Dog extends Animal {
  bark(): void {
    // console.log(this.dna);    // ❌ 子类也不能访问 private
    console.log(this.getDNA());  // ✅ 但可以通过父类的 protected 方法间接访问
  }
}

跨实例 private 访问

TypeScript 允许同一个类的不同实例之间互相访问 private 成员

class Point {
  private x: number;
  private y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  equals(other: Point): boolean {
    return this.x === other.x && this.y === other.y; // ✅ 可以访问 other 的 private 成员
  }
}

这个行为跟 Java、C#、C++ 一致(Ruby 不一样,Ruby 不允许)。

private 的真相:编译期检查,运行时裸奔

这是 TypeScript 初学者最容易踩的坑:private 只在编译时生效,编译成 JavaScript 后完全不存在。

class Secret {
  private key = "abc123";
}

const s = new Secret();
// TypeScript 编译时报错:
// console.log(s.key);

// 但编译后的 JS 里,key 就是一个普通属性
// 在 JS 代码里访问 s.key 完全不会报错
console.log((s as any).key);       // 类型断言可以绕过
console.log(s["key"]);             // 方括号访问也能绕过
Caution

TypeScript 的 private 是”软私有”(soft private)。如果你需要真正的运行时保护,不要依赖它。下面马上讲替代方案。

protected:子类可见

protected 是 private 的”放宽版”——类内部和子类可以访问,但外部不能:

class Person {
  protected name: string;

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

class Employee extends Person {
  private department: string;

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

  introduce(): string {
    return `我是 ${this.name},在 ${this.department} 工作`; // ✅ 子类可以访问 protected
  }
}

const emp = new Employee("Alice", "技术部");
console.log(emp.introduce());  // "我是 Alice,在 技术部 工作"
// console.log(emp.name);       // ❌ 外部不能访问 protected

子类可以提升 protected 的可见性

子类重写父类的 protected 成员时,可以把可见性提升为 public:

class Base {
  protected m = 10;
}

class Derived extends Base {
  m = 15;  // 没写修饰符,默认 public
}

const d = new Derived();
console.log(d.m); // 15 —— 没问题,m 现在是 public

如果你想保持 protected,重写时记得再加一次 protected

跨层级的 protected 限制

TypeScript 不允许在不同分支的子类之间互相访问 protected 成员:

class Base {
  protected x = 1;
}

class Derived1 extends Base {
  protected x = 5;
}

class Derived2 extends Base {
  f1(other: Derived2) {
    other.x = 10;  // ✅ 同一个类的实例,可以
  }

  f2(other: Derived1) {
    // other.x = 10;  // ❌ Derived1 不是 Derived2 的子类
  }
}

TypeScript private vs JavaScript # 私有字段

前面说了 TypeScript 的 private 是软私有,运行时挡不住。如果你需要真正的硬私有,用 JavaScript 的原生私有字段 #

class Wallet {
  #balance = 0;     // JS 原生私有字段

  deposit(amount: number): void {
    if (amount > 0) {
      this.#balance += amount;
    }
  }

  getBalance(): number {
    return this.#balance;
  }
}

const wallet = new Wallet();
wallet.deposit(100);
console.log(wallet.getBalance()); // 100

// 以下全部无效——运行时也访问不到
// console.log(wallet.#balance);     // ❌ 编译+运行时都报错
// console.log(wallet["#balance"]);  // undefined

两者怎么选?看这张表:

维度private(TS)#(JS 原生)
生效时机仅编译时编译时 + 运行时
单元测试中访问可以用 ["prop"] 绕过完全无法访问
子类继承时冲突报错(不能重写)可以(独立作用域)
编译目标兼容性所有版本ES2022+(旧版会用 WeakMap 降级)
性能无运行时开销极微的 WeakMap 开销(降级时)

什么时候用哪个

private(TS 修饰符):

  • 你只需要在编译阶段防止意外访问,不需要防恶意代码。
  • 项目需要兼容旧版 JS 运行环境。
  • 在单元测试中需要偶尔”偷看”内部状态。

#(JS 原生):

  • 你写的是库或 SDK,需要防止外部代码访问内部状态。
  • 数据确实敏感(密钥、令牌等),不能让任何人碰。
  • 你需要子类可以声明同名的私有字段而不冲突。
Tip

大多数应用代码的场景下,TypeScript 的 private 已经够用了——你的同事不会故意绕过类型检查来访问你的私有属性。但如果你在写一个公开的 npm 包,就应该用 #

三种修饰符总结

修饰符类内部子类类外部
public(默认)
protected
private
Tip

一个实用原则:默认用 private,当子类需要访问时改成 protected,当外部确实需要访问时才用 public最小权限原则在类设计里同样适用。

小结

  • public 是默认,哪里都能访问。
  • private 仅在类内部可访问,编译后消失。
  • protected 类内部和子类可访问,外部不能。
  • TS 的 private 是软私有,运行时挡不住。需要真正保护用 JS 的 # 私有字段。
  • 默认用 private,按需放宽到 protectedpublic

访问修饰符让领域有了封装,但还有另一个维度的”不可变”需求——有些属性创建之后就不该被改。下一章讲 readonly 和参数属性的简写语法。