在 JavaScript ES6 之前,开发者们通过原型链(prototypal inheritance)来模拟面向对象的编程范式。虽然功能强大,但其语法和心智模型对于有传统面向对象语言(如 Java 或 C#)背景的开发者来说,常常显得不够直观。ES6 引入了 class 关键字,提供了一套更清晰、更熟悉的语法糖。而 TypeScript 则在此基础上,更进一步,引入了完整的类型支持和强大的访问控制机制,将 JavaScript 的面向对象能力提升到了一个全新的工程化高度。
本文将深入探讨 TypeScript 中类的定义,并重点解析其访问控制的核心——public、private 和 protected 修饰符。
类的基本解剖:蓝图的构建
在面向对象编程中,类 (Class) 是创建对象的蓝图 (Blueprint)。它定义了一类事物所共有的属性 (Properties)(状态)和方法 (Methods)(行为)。
让我们通过构建一个简单的 Player 类来理解其基本结构:
class Player {
// 1. 属性 (Properties)
username: string;
health: number;
isOnline: boolean;
// 2. 构造函数 (Constructor)
constructor(username: string) {
this.username = username;
this.health = 100; // 初始生命值为 100
this.isOnline = true;
console.log(`Welcome, ${this.username}!`);
}
// 3. 方法 (Methods)
attack(target: Player): void {
if (!this.isOnline || !target.isOnline) {
console.log("One of the players is offline.");
return;
}
console.log(`${this.username} attacks ${target.username}!`);
// ... 复杂的伤害计算逻辑
}
disconnect(): void {
this.isOnline = false;
console.log(`${this.username} has disconnected.`);
}
}这个 Player 类包含了构成一个类的三个核心部分:
- 属性:我们在类的主体中声明了
username,health,isOnline等属性,并为它们指定了类型。这些属性构成了每个Player实例的内部状态。 - 构造函数:
constructor是一个特殊的方法,它在通过new关键字创建类的新实例时被自动调用。它的主要职责是初始化对象的属性。 - 方法:
attack和disconnect是定义在类上的函数,它们描述了Player对象能够执行的行为。方法可以访问和修改实例的属性(通过this关键字)。
创建和使用类的实例(即对象)非常直观:
const player1 = new Player("Alice");
const player2 = new Player("Bob");
player1.attack(player2); // 输出: Alice attacks Bob!
player2.disconnect(); // 输出: Bob has disconnected.访问修饰符:封装的艺术
仅仅定义结构是不够的。一个健壮的系统还需要封装 (Encapsulation)——即隐藏对象的内部实现细节,只暴露必要的接口供外部使用。这可以防止外部代码随意篡改对象的状态,从而保证系统的稳定性和可维护性。
TypeScript 为此提供了三个访问修饰符:public、private 和 protected。
1. public (公开的)
public 是最宽松的访问级别,也是默认的修饰符。如果你不显式指定任何修饰符,那么该成员就是 public 的。被标记为 public 的属性或方法可以在任何地方被访问——包括类的内部、类的实例外部,以及子类中。
class Greeter {
public message: string; // 显式标记为 public
constructor(message: string) {
this.message = message;
}
}
const greeter = new Greeter("Hello");
console.log(greeter.message); // 可以在外部访问2. private (私有的)
private 是最严格的访问级别。被标记为 private 的成员只能在定义它的那个类的内部被访问。类的实例外部和子类都无法访问。这对于隐藏那些不应被外部知晓的内部状态或辅助方法至关重要。
class BankAccount {
private balance: number = 0;
constructor(initialBalance: number) {
if (this.isValidAmount(initialBalance)) {
this.balance = initialBalance;
}
}
public deposit(amount: number): void {
if (this.isValidAmount(amount)) {
this.balance += amount;
console.log(`Deposited ${amount}. New balance: ${this.balance}`);
}
}
// 这是一个私有的辅助方法,外部无需关心其实现
private isValidAmount(amount: number): boolean {
return amount > 0;
}
}
const account = new BankAccount(100);
account.deposit(50);
// console.log(account.balance);
// 编译时错误: Property 'balance' is private and only accessible within class 'BankAccount'.
// account.isValidAmount(10);
// 编译时错误: Property 'isValidAmount' is private...3. protected (受保护的)
protected 是介于 public 和 private 之间的一种访问级别。被标记为 protected 的成员可以在定义它的类的内部以及该类的子类中被访问。但是,它不能在类的实例外部被访问。
这对于创建可被子类扩展和定制的基类非常有用。
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
public bark(): void {
// 可以在子类中访问受保护的成员
console.log(`Woof! My name is ${this.name}.`);
}
}
const dog = new Dog("Buddy");
dog.bark(); // 输出: Woof! My name is Buddy.
// console.log(dog.name);
// 编译时错误: Property 'name' is protected and only accessible within class 'Animal' and its subclasses.实践捷径:参数属性
TypeScript 提供了一种非常便利的语法糖,叫做参数属性 (Parameter Properties)。它允许我们在构造函数的参数上直接使用访问修饰符,从而将参数的声明、赋值和属性的定义合并为一步。
传统方式:
class Person {
public name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}使用参数属性的简洁方式:
class Person {
constructor(public name: string, public age: number) {
// 构造函数体可以是空的,因为声明和赋值已经自动完成
}
}这两种写法在功能上是完全等价的,但第二种显然更加简洁高效,是 TypeScript 开发中的常用技巧。
总结
类是 TypeScript 实现结构化和面向对象编程的核心。它通过将数据(属性)和行为(方法)封装在一起,为我们提供了构建复杂系统的坚实基础。
- 类是创建对象的蓝图,包含属性、构造函数和方法。
- 访问修饰符是实现封装的关键:
public:无限制访问(默认)。private:仅限类内部访问。protected:类内部及其子类可以访问。
- 参数属性是简化类定义的实用语法糖。
通过熟练运用类及其访问控制机制,我们可以编写出更模块化、更安全、更易于维护和扩展的高质量代码。
