类魔方 :多变组合,灵活复用
文章目录
- 一、类的基础
- 1. 类的基本结构与语法
- 1. 类的定义与实例化
- 2. 成员变量(属性)
- 3. 构造函数(Constructor)
- 4. 成员方法
- 2. 访问修饰符
- 1. 基本访问规则
- 2. 子类对父类方法的重写
- 3. 构造函数的访问修饰符
- 4. 参数属性与继承
- 总结
- 3. 接口与实现
- 1、接口的定义(Declaration)
- 2、类对接口的实现(Implementation)
- 3、接口与抽象类的区别
- 4、多接口实现
- 5、接口的核心作用
- 总结
- 4. 静态成员与实例成员
- 1. 静态成员(Static Members)
- 2. 实例成员(Instance Members)
- 对比
- 注意
- 5. 访问器(Getter/Setter)
- 1. 基本语法
- 2. 核心特点
- 3. 使用场景
- 4. 注意事项
- 6. 类的高级特性
- 1、泛型类(Generic Classes)
- 作用
- 语法
- 2、装饰器(Decorators)
- 作用
- 核心类型
- 3、参数属性(Parameter Properties)
- 作用
- 语法
- 关键点总结
- 二、类的提高
- 1. 抽象类(Abstract Classes)
- 1. 抽象类的定义
- 2. 抽象类的特点
- 3. 抽象属性
- 2. 类的多态(Polymorphism)
- 1. 多态的核心实现方式
- 场景一:继承与方法重写
- 场景二:接口实现
- 2. 多态的关键特性
- 3. 多态与重载的区别
- 4. 多态的优势
- 3. 类的类型兼容性
- 1. 核心规则
- (1)子类与父类的兼容性
- (2)无继承关系的类
- 2. 成员的兼容性细节
- (1)属性兼容性
- (2)方法兼容性
- 3. 特殊成员的影响
- (1)私有 / 受保护成员
- (2)构造函数
- 总结
- 4. 类的受保护构造函数
- 1. 受保护构造函数的作用
- (1)禁止外部实例化,强制通过子类创建对象
- 2. 受保护构造函数的特点
- 3. 实际应用场景
- (1)工厂模式
- (2)防止基础类被滥用
- 5. 注意事项
- 5. 类的私有字段(ES2022 语法)
- 1. 基本语法
- 2. 与 `private` 修饰符的区别
- 3. 私有字段的特性
- (1)运行时强制访问限制
- (2)不允许子类访问
- (3)严格的名称唯一性
- 4. 私有字段的应用场景
- (1)数据封装与隐藏
- (2)防止命名冲突
- (3)实现类内部状态
- 5. 注意事项
- 6. 类的构造函数重载(Constructor Overloading)
- 1. 构造函数重载的语法
- 2. 重载签名与实现的约束
- 3. 与可选参数的对比
- 4. 常见应用场景
- (1)创建工厂模式
- (2)处理不同数据格式
- 5. 注意事项
- 7. 类的静态块(Static Blocks)
- 1. 静态块的特性
- (1)仅执行一次
- (2)可访问私有字段
- (3)支持多个静态块
- 2. 与静态构造函数的对比
- 3. 注意事项
一、类的基础
1. 类的基本结构与语法
1. 类的定义与实例化
类是对象的蓝图,通过class
关键字定义,使用new
关键字创建实例。
class Person {// 类的主体
}const person = new Person(); // 创建类的实例
2. 成员变量(属性)
类中可以定义数据字段(属性),用于存储实例或类的状态。
class Person {name: string; // 实例属性age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}static species = "Homo sapiens"; // 静态属性,属于类本身
}const person = new Person("abc",18);
person.name = "Alice"; // 访问实例属性
person.age = 30;// console.log(person.species);console.log(Person.species); // 访问静态属性,通过类名调用
3. 构造函数(Constructor)
特殊方法,用于初始化对象的状态,创建实例时自动调用。
class Person {name: string;age: number;constructor(name: string, age: number) {this.name = name; // 使用this引用当前实例this.age = age;}
}const person = new Person("Bob", 25); // 传递参数给构造函数
4. 成员方法
类中定义的函数,用于实现对象的行为。
class Person {constructor(public name: string, public age: number) {}greet() {return `Hello, I'm ${this.name}, ${this.age} years old.`;}static describeSpecies() { // 静态方法return "All persons are Homo sapiens.";}
}const person = new Person("Charlie", 35);
console.log(person.greet()); // 调用实例方法
console.log(Person.describeSpecies()); // 调用静态方法
2. 访问修饰符
-
public
(默认)- 可被任意访问(类内部、子类、外部)。
class Person {public name: string; // 显式声明publicage: number; // 默认public }
-
private
- 只能在类内部访问,子类和外部不可访问。
class Employee {private salary: number; // 私有属性getSalary() {return this.salary; // 类内部可访问} }
-
protected
- 类内部和子类可访问,外部不可访问。
class Animal {protected name: string; } class Dog extends Animal {getName() {return this.name; // 子类可访问} }
-
readonly
- 属性只能在初始化时赋值,之后不可修改。
class Point {readonly x: number;constructor(x: number) {this.x = x; // 初始化时赋值} }
1. 基本访问规则
修饰符 | 类内部 | 子类 | 外部 |
---|---|---|---|
public | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ❌ |
private | ✅ | ❌ | ❌ |
2. 子类对父类方法的重写
- 子类可以重写父类的方法,但访问修饰符不能更严格。
- 例如:父类的
protected
方法,子类重写时只能是protected
或public
。
- 例如:父类的
class Vehicle {protected startEngine() {console.log("Engine started");}
}class Car extends Vehicle {// ✅ 正确:重写为 public(更宽松)public startEngine() {console.log("Car engine started");}// ❌ 错误:重写为 private(更严格)// private startEngine() { ... }
}
// <html>TS2415: Class 'Car' incorrectly extends base class 'Vehicle'.<br/>Property 'startEngine' is private in type 'Car' but not in type 'Vehicle'.
3. 构造函数的访问修饰符
private
构造函数:禁止外部实例化,可用于实现单例模式、工具类。
单例模式(Singleton Pattern)是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。
class Singleton {private static instance: Singleton; // 静态属性存储唯一实例private constructor() {// 私有构造函数,防止外部实例化}static getInstance(): Singleton {if (!Singleton.instance) {Singleton.instance = new Singleton(); // 首次调用时创建实例}return Singleton.instance;}// 其他方法public someMethod() {console.log("Singleton method called");}
}// const s = new Singleton(); // 错误:无法实例化// 使用示例
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true,两个引用指向同一个实例
protected
构造函数:禁止外部实例化,但允许子类继承。
// 受保护构造函数示例
class Base {protected constructor() {}
}class Derived extends Base {} // 正确:子类可继承
// const b = new Base(); // 错误:无法直接实例化
4. 参数属性与继承
- 参数属性(构造函数中直接定义的属性)同样受访问修饰符控制。
class Person {constructor(public name: string, // 公开属性protected age: number, // 受保护属性private salary: number // 私有属性) {}
}class Employee extends Person {constructor(name: string, age: number, salary: number, public department: string) {super(name, age, salary);}showInfo() {console.log(`${this.name}, ${this.age}`); // 可访问 public/protected// console.log(this.salary); // 错误:无法访问 private}
}
总结
场景 | public | protected | private |
---|---|---|---|
类内部访问 | ✅ | ✅ | ✅ |
子类访问 | ✅ | ✅ | ❌ |
外部访问 | ✅ | ❌ | ❌ |
子类重写方法的限制 | 无 | 不能更严格 | 不可重写 |
构造函数修饰 | 可实例化 | 仅子类可继承 | 仅类内部可实例化 |
3. 接口与实现
在面向对象编程中,接口(Interface)用于定义类的公共契约(方法、属性的声明),而实现(Implementation) 则是类对接口中声明的具体代码实现。
1、接口的定义(Declaration)
接口仅规定类必须包含的成员(方法名、参数类型、返回类型等),不包含具体实现逻辑。
interface Animal {name: string; // 属性声明speak(): string; // 方法声明(无函数体)age?: number; // 可选属性(非必填)
}
2、类对接口的实现(Implementation)
类通过 implements
关键字声明遵循某个接口,并必须实现接口中所有必填成员。
// 实现接口 Animal
class Dog implements Animal {// 必须实现接口中的 name 属性constructor(public name: string) {} // 必须实现接口中的 speak 方法speak(): string { return "Woof!";}// 可选属性(可实现也可不实现)age?: number;
}
3、接口与抽象类的区别
特性 | 接口(Interface) | 抽象类(Abstract Class) |
---|---|---|
是否可实例化 | 完全抽象,不可实例化 | 不可实例化(但可包含具体实现的属性或方法) |
成员类型 | 仅声明(属性、方法、索引签名等) | 可包含抽象成员和具体实现 |
继承方式 | implements (可实现多个接口) | extends (仅继承一个抽象类) |
强制约束 | 必须实现所有声明的成员 | 必须实现抽象成员,可继承具体成员 |
4、多接口实现
一个类可以实现多个接口,需满足所有接口的要求。
interface Swimmable {swim(): string;
}interface Runable {run(speed: number): string;
}// 实现两个接口
class Duck implements Animal, Swimmable, Runable { }
5、接口的核心作用
- 规范契约:确保不同类遵循统一的方法 / 属性定义,便于团队协作和代码维护。
- 类型检查:编译器会强制类实现接口声明的成员,避免运行时错误。
- 解耦依赖:代码可依赖接口而非具体类,提高可扩展性(如依赖倒置原则)。
// 依赖接口而非具体类(面向接口编程)
function introduce(animal: Animal) {return `${animal.name} says ${animal.speak()}`;
}introduce(new Dog("Buddy")); // "Buddy says Woof!"
introduce(new Duck("Donald")); // "Donald says Quack!"
总结
- 接口是契约的抽象声明,规定 “必须有什么”;
- 类的实现是契约的具体落地,规定 “如何实现”;
- 通过
implements
关键字关联接口与类,确保类型安全和代码规范。
4. 静态成员与实例成员
1. 静态成员(Static Members)
- 归属:属于类本身,而非类的实例。
- 访问方式:通过类名直接调用(如
Class.staticMethod()
)。 - 特点:
- 所有实例共享同一静态成员。
- 不能访问实例属性或方法(无
this
指向实例)。
2. 实例成员(Instance Members)
- 归属:属于类的实例,每个实例独立拥有。
- 访问方式:通过实例调用(如
instance.method()
)。 - 特点:
- 每个实例的属性值可不同。
- 可访问实例属性和静态成员。
对比
特性 | 静态成员 | 实例成员 |
---|---|---|
归属 | 类本身 | 类的实例 |
访问方式 | Class.member | instance.member |
this 指向 | 类本身 | 当前实例 |
共享性 | 所有实例共享同一值 | 每个实例独立存储 |
使用场景 | 工具方法、全局配置、单例模式 | 对象的状态和行为 |
注意
- 静态属性初始化:静态属性在类加载时初始化,早于实例创建。
- 静态方法限制:静态方法无法直接访问实例属性,需通过参数传递实例。
5. 访问器(Getter/Setter)
1. 基本语法
- Getter:读取属性值时调用的方法(
get
关键字)。 - Setter:设置属性值时调用的方法(
set
关键字)。
class User {// private _age: number;constructor(private _age: number) {}// Getter:读取 age 属性get age() {return this._age;}// Setter:设置 age 属性set age(value: number) {if (value < 0) throw new Error("Age cannot be negative");this._age = value;}
}const user = new User(5);// user.age = -30; // 如果没有try-catch块调用 setter,触发错误,程序在此处终止// try 块中抛出错误位置之后的代码会被中断,但catch块和后续代码会继续执行。
try {user.age = -30;
} catch (err: unknown) {if (err instanceof Error) {console.error(err.message + ' failed');} else {console.error(`Unexpected error type: ${err}`);}
} finally {console.log(user.age);
}console.log(user.age);
2. 核心特点
特性 | Getter | Setter |
---|---|---|
语法 | get propertyName() | set propertyName(value) |
参数 | 无参数 | 必须有且仅有一个参数 |
返回值 | 必须有返回值 | 不能有返回值(void) |
调用方式 | 像访问属性一样调用(无括号) | 像赋值一样调用(obj.prop = value ) |
3. 使用场景
- 数据验证:在赋值时校验数据有效性(如示例中的年龄不能为负数)。
- 计算属性:动态计算属性值(如根据其他属性计算)。
- 访问控制:隐藏内部实现细节,提供受控访问。
4. 注意事项
- 必须成对出现:在严格模式下,通常需要同时定义 getter 和 setter。
- 与普通属性的区别:访问器会拦截属性的读写操作,而普通属性直接存储值。
- 兼容性:编译为 ES5 及以下版本时,需使用
Object.defineProperty
实现。
6. 类的高级特性
- 泛型类:支持类的属性或方法使用泛型类型。
- 装饰器(Decorators):修改类的行为(实验性特性)。
- 参数属性:在构造函数中直接定义并初始化属性。
1、泛型类(Generic Classes)
作用
使类的属性、方法支持动态类型,提高复用性。
语法
在类名后声明泛型参数(如 <T>
),并在成员中使用。
class Box<T> { // 泛型类:T 为任意类型constructor(public value: T) {}showType(): string {return `Type of value: ${typeof this.value}`;}
}// 使用示例
const numberBox = new Box<number>(123); // 类型为 number
const stringBox = new Box<string>("Hello"); // 类型为 stringconsole.log(numberBox.showType()); // "Type of value: number"
2、装饰器(Decorators)
作用
在不修改类本身的情况下,动态添加或修改类的行为(需启用 experimentalDecorators
编译选项)。
核心类型
- 类装饰器:修改类的原型或静态属性。
- 方法装饰器:修改方法的特性(如添加日志)。
- TypeScript 类型检查:仅基于类的静态定义(如
User
类中并未声明isLogged
)。- 装饰器的动态性:装饰器在运行时修改类的原型,这种修改无法被 TypeScript 的类型系统自动捕获。(报错但可运行)
// 其作用是在运行时修改类的原型(prototype),为类的所有实例添加一个 isLogged 属性
function Logged(constructor: Function) {constructor.prototype.isLogged = true;
}@Logged
class User {constructor(public name: string) {}
}// 使用接口合并(Interface Merging)扩展User类的类型定义
interface User {isLogged: boolean; // 显式声明装饰器添加的属性
}const user = new User("Alice");
console.log(user.isLogged); // ✅ 类型检查通过// isLogged 是原型属性(非实例属性),所有实例共享同一个值。
// 实例可直接访问该属性,但修改它会创建实例自己的属性(遮蔽原型属性)。
const user1 = new User("Bob");
const user2 = new User("Charlie");console.log(user1.isLogged); // true(继承自原型)
user1.isLogged = false; // 仅修改 user1 自己的属性console.log(user1.isLogged); // false(实例属性)User.prototype.isLogged = false;console.log(user2.isLogged); // false(原型属性改变)
3、参数属性(Parameter Properties)
作用
在构造函数中直接声明并初始化属性,省略属性声明代码。
语法
在构造函数参数前添加访问修饰符(public
/private
/protected
/readonly
)。
class Person {// 等价于:// public name: string;// private age: number;// readonly id: string;constructor(public name: string, // 公开属性private age: number, // 私有属性readonly id: string // 只读属性) {}
}
关键点总结
特性 | 核心语法 / 示例 | 用途 / 场景 |
---|---|---|
泛型类 | class Box<T> { value: T; } | 集合、工具类、类型安全封装 |
装饰器 | @Decorator 语法,修改类行为 | 日志、权限控制、元数据管理 |
参数属性 | constructor(public prop: Type) {} | 快速初始化简单属性 |
二、类的提高
1. 抽象类(Abstract Classes)
在 TypeScript 中,抽象类(Abstract Class)是一种不能直接实例化的类,它为子类提供通用的属性和方法定义。抽象类可以包含抽象成员(未实现的方法或属性),强制子类必须实现这些成员。
1. 抽象类的定义
使用 abstract
关键字声明类和抽象成员。子类通过 extends
关键字继承抽象类,并实现所有抽象成员。
abstract class Animal {// 实例属性(非抽象)constructor(public name: string) {}// 抽象方法:子类必须实现abstract speak(): string;// 具体方法:子类可直接继承或重写move(): string {return `${this.name} is moving`;}
}class Dog extends Animal {// 实现抽象方法 speakspeak(): string {return "Woof!";}// 重写具体方法 move(可选)move(): string {return `${this.name} is running`;}
}
2. 抽象类的特点
特性 | 抽象类 | 普通类 |
---|---|---|
实例化 | ❌ 不能直接实例化 | ✅ 可以实例化 |
抽象成员 | ✅ 允许包含抽象方法 / 属性 | ❌ 不能包含抽象成员 |
子类约束 | 子类必须实现所有抽象成员 | 无强制约束 |
使用场景 | 定义基类接口,强制子类遵循契约 | 具体实现类 |
3. 抽象属性
抽象类中可以声明抽象属性(无初始值,子类必须实现)。
abstract class Vehicle {abstract wheels: number; // 抽象属性abstract start(): void;getWheelCount(): number {return this.wheels; // 使用抽象属性}
}class Car extends Vehicle {wheels = 4; // 实现抽象属性start(): void {console.log("Car started");}
}
2. 类的多态(Polymorphism)
在面向对象编程中,多态(Polymorphism)指的是不同类的对象对同一消息(方法调用)作出不同响应的能力。TypeScript 通过继承和接口实现来支持多态,核心是子类重写父类方法或实现接口方法。以下是关键概念和示例:
1. 多态的核心实现方式
场景一:继承与方法重写
父类定义方法,子类 重写(Override) 该方法以实现不同逻辑。
class Animal {speak(): string {return "Animal sound";}
}class Dog extends Animal {override speak(): string { // 重写父类方法return "Woof!";}
}class Cat extends Animal {override speak(): string { // 重写父类方法return "Meow!";}
}
场景二:接口实现
不同类实现同一接口,对接口方法作出不同实现。
interface Shape {calculateArea(): number;
}class Circle implements Shape {constructor(public radius: number) {}calculateArea(): number {return Math.PI * this.radius ** 2;}
}class Rectangle implements Shape {constructor(public width: number, public height: number) {}calculateArea(): number {return this.width * this.height;}
}
2. 多态的关键特性
特性 | 说明 |
---|---|
类型兼容性 | 子类对象可赋值给父类(如 Animal animal = new Dog(); )或const a: Animal = new Dog(); |
动态绑定 | 方法调用的具体实现由对象的运行时类型决定,而非编译时类型 |
解耦性 | 高层模块依赖抽象(父类或接口),而非具体子类,符合依赖倒置原则 |
里氏替换原则(LSP)
“子类对象必须能够替换其父类对象而不影响程序的正确性。”
3. 多态与重载的区别
特性 | 多态(Polymorphism) | 重载(Overloading) |
---|---|---|
定义 | 不同对象对同一方法的不同实现 | 同一类中多个同名方法,参数列表不同 |
实现方式 | 继承 / 接口实现 | 方法参数类型、数量或顺序不同 |
作用 | 统一接口,差异化实现 | 方便调用,适应不同参数组合 |
4. 多态的优势
- 可扩展性:新增子类无需修改现有逻辑(符合开闭原则)。
- 代码复用:通过父类或接口统一管理子类对象。
- 类型安全:TypeScript 编译器确保子类实现所有必要方法。
3. 类的类型兼容性
在 TypeScript 中,类的类型兼容性基于结构子类型(Structural Subtyping),而非名义子类型(Nominal Subtyping)。这意味着:
若两个类的结构(属性和方法)兼容,则它们类型兼容,无论是否存在继承关系。
1. 核心规则
(1)子类与父类的兼容性
- 子类 → 父类:始终兼容(子类包含父类的所有成员)。
- 父类 → 子类:不兼容(父类缺少子类的额外成员)。
(2)无继承关系的类
若两个类的结构相同,则它们类型兼容:
class Point1 {x: number;y: number;
}class Point2 {x: number;y: number;
}let p1: Point1 = new Point2(); // ✅ 结构相同,类型兼容
2. 成员的兼容性细节
(1)属性兼容性
- 必须存在:目标类型的所有属性必须在源类型中存在。
- 类型兼容:对应属性的类型必须兼容(协变)。
class A { x: number; }
class B { x: number; y: string; }let a: A = new B(); // ✅ B 包含 A 的所有属性
let b: B = new A(); // ❌ A 缺少 B 的 y 属性
(2)方法兼容性
- 参数逆变:源类型的方法参数类型必须是目标类型的超类型。
如果类型
A
可以赋值给类型B
,则称A
是B
的子类型(A ≤ B
),或B
是A
的超类型(B ≥ A
)。
- 返回值协变:源类型的返回值类型必须是目标类型的子类型。
协变规则(返回值类型)
源类型(被赋值的类型)的返回值类型必须是目标类型(赋值的目标类型)返回值类型的子类型。
class Animal { move(): void {} }
class Dog extends Animal { move(distance: number): void {} }let a: Animal = new Dog(); // ✅ Dog.move 参数更具体(兼容逆变)
let d: Dog = new Animal(); // ❌ Animal.move 缺少 distance 参数(不兼容)
3. 特殊成员的影响
(1)私有 / 受保护成员
若类包含私有或受保护成员,则仅当这些成员来自同一类定义时,类型才兼容:
class Animal {private name: string; // 私有成员
}class Dog extends Animal {}
class Cat {private name: string; // 不同类的私有成员
}let d: Dog = new Animal(); // ✅ 父子类私有成员同源
let a: Animal = new Cat(); // ❌ 私有成员不同源
(2)构造函数
构造函数不影响类型兼容性,仅关注实例成员:
class A { constructor(x: number) {} }
class B { constructor(y: string) {} }let a: A = new B(); // ✅ 构造函数不参与类型检查
总结
TypeScript 的类类型兼容性基于结构匹配,而非名义关系:
- 子类与父类:子类可赋值给父类,但反之不可。
- 无继承关系的类:结构相同则兼容。
- 私有 / 受保护成员:必须来自同一类定义。
- 方法参数:遵循逆变规则(源类型参数更宽泛)。
4. 类的受保护构造函数
在 TypeScript 中,受保护构造函数(protected constructor
)用于限制类的实例化范围,仅允许在当前类或其子类中调用。当不需要继承其他类时,受保护构造函数可以实现。
1. 受保护构造函数的作用
(1)禁止外部实例化,强制通过子类创建对象
class Animal {protected constructor(public name: string) {} // 受保护构造函数
}// ❌ 错误:无法在类外部实例化受保护构造函数的类
// const animal = new Animal("Buddy"); class Dog extends Animal {constructor(name: string) {super(name); // ✅ 子类中可调用父类的受保护构造函数}
}const dog = new Dog("Buddy"); // ✅ 正确:通过子类实例化
2. 受保护构造函数的特点
修饰符 | 实例化范围 | 子类继承 | 接口实现 |
---|---|---|---|
public | 任何地方均可实例化 | 允许 | 无影响 |
protected | 仅当前类或子类中可实例化 | 允许 | 无影响 |
private | 仅当前类内部可实例化 | 禁止继承 | 无影响 |
3. 实际应用场景
(1)工厂模式
通过受保护构造函数隐藏实例化细节,强制通过工厂方法创建对象:
class User {protected constructor(public id: number, public name: string) {}static createUser(name: string): User {// 内部逻辑生成 idreturn new User(1, name); // ✅ 类内部可调用受保护构造函数}
}// ❌ 错误:无法直接实例化
// const user = new User(1, "Alice"); const user = User.createUser("Alice"); // ✅ 通过工厂方法创建
(2)防止基础类被滥用
确保只有特定子类可以创建实例:
class Vehicle {protected constructor(public wheels: number) {}
}class Car extends Vehicle {constructor() {super(4); // 汽车固定为 4 个轮子}
}class Bicycle extends Vehicle {constructor() {super(2); // 自行车固定为 2 个轮子}
}
5. 注意事项
-
子类必须调用
super()
:
子类构造函数中必须显式调用父类的受保护构造函数,否则编译报错。 -
类型兼容性:
包含受保护构造函数的类仍可作为父类型使用:const vehicle: Vehicle = new Car(); // 多态赋值,类型兼容
-
无法通过接口强制约束:
接口不能包含构造函数,因此无法通过接口强制子类实现受保护构造函数。
5. 类的私有字段(ES2022 语法)
在 TypeScript 中,私有字段(Private Fields) 是 ES2022 引入的语法,使用 #
前缀声明类的私有成员。与传统的 private
修饰符不同,私有字段具有更强的封装性,在运行时直接限制访问
1. 基本语法
使用 #
前缀声明私有字段,只能在类内部访问:
class Person {#name: string; // 私有字段constructor(name: string) {this.#name = name;}greet() {return `Hello, my name is ${this.#name}`; // 类内部可访问}
}const person = new Person("Alice");
console.log(person.greet()); // "Hello, my name is Alice"
console.log(person.#name); // 错误:私有字段无法在类外部访问
2. 与 private
修饰符的区别
特性 | 私有字段 (#field ) | private 修饰符 |
---|---|---|
访问限制 | 运行时强制限制,外部无法访问 | 仅编译时检查,运行时可绕过 |
作用域 | 严格绑定到当前类 | 子类中不可访问,但可通过反射访问 |
兼容性 | ES2022+,需编译降级 | 所有 TypeScript 版本支持 |
命名冲突 | 每个类独有,不与父类 / 子类冲突 | 子类可定义同名成员(隐藏父类) |
3. 私有字段的特性
(1)运行时强制访问限制
私有字段在运行时直接阻止外部访问:
class Secret {#key = "12345";getKey() {return this.#key;}
}const secret = new Secret();
console.log(secret.getKey()); // "12345"
console.log(secret.#key); // ❌ 语法错误:无法访问私有字段
(2)不允许子类访问
即使在子类中也无法直接访问父类的私有字段:
class Parent {#privateField = "Parent secret";
}class Child extends Parent {accessParentField() {console.log(this.#privateField); // ❌ 错误:无法访问父类私有字段}
}
(3)严格的名称唯一性
同一类中不能定义同名的私有字段和公共属性:
class Example {#name = "Alice";name = "Bob"; // ❌ 错误:名称冲突(即使一个是私有字段)
}
4. 私有字段的应用场景
(1)数据封装与隐藏
保护敏感数据不被外部直接访问:
class BankAccount {#balance: number;constructor(initialBalance: number) {this.#balance = initialBalance;}deposit(amount: number) {this.#balance += amount;}// 仅通过公共方法访问私有字段getBalance() {return this.#balance;}
}
(2)防止命名冲突
在继承体系中避免子类意外覆盖父类成员:
class Base {#internalId = "base123";
}class Sub extends Base {#internalId = "sub456"; // ✅ 允许,私有字段名称独立
}
(3)实现类内部状态
存储仅用于类内部逻辑的状态:
class Counter {#count = 0;increment() {this.#count++;}getCount() {return this.#count;}
}
5. 注意事项
-
不能在类外声明类型:
私有字段的类型只能在类内部声明:class User {#age: number; // ✅ 正确 }// ❌ 错误:无法在类外为私有字段添加类型注解 type User = {#age: number; }
-
性能考虑:
私有字段在 JavaScript 中通过 WeakMap 实现,可能略慢于普通属性,但通常可忽略不计。
6. 类的构造函数重载(Constructor Overloading)
在 TypeScript 中,构造函数重载(Constructor Overloading)允许类的构造函数根据不同参数类型和数量提供多种调用方式。虽然 TypeScript 支持构造函数重载语法,但实现时只能有一个具体的构造函数,需要在单个实现中处理所有重载签名。
1. 构造函数重载的语法
- 声明多个重载签名:定义不同参数组合的构造函数类型。
- 实现单个构造函数:使用最宽泛的参数类型实现,并在内部处理所有情况。
class Point {x: number;y: number;// 重载签名 1:无参数constructor();// 重载签名 2:两个数值参数constructor(x: number, y: number);// 重载签名 3:对象参数constructor(coords: { x: number; y: number });// 实际实现(必须兼容所有重载签名)constructor(x?: number | { x: number; y: number }, y?: number) {if (typeof x === 'object') {this.x = x.x;this.y = x.y;} else {// 空值合并操作符:若 x 为 undefined 或 null,则返回 0,否则返回 x。this.x = x ?? 0;this.y = y ?? 0;}}
}// 使用不同重载创建实例
const p1 = new Point(); // x=0, y=0
const p2 = new Point(1, 2); // x=1, y=2
const p3 = new Point({ x: 3, y: 4 }); // x=3, y=4
- 当
x
是对象时:从对象中提取x
和y
属性赋值给实例。 - 当
x
是其他类型(如数值)时:直接使用x
和y
参数,若参数为undefined
或null
则默认赋值为0
。
2. 重载签名与实现的约束
-
实现必须兼容所有重载签名:
实现的参数类型必须是所有重载签名参数类型的联合类型。class Person {// 重载签名constructor(name: string);constructor(name: string, age: number);// 实现(参数类型必须能处理所有重载情况)constructor(name: string, age?: number) {// ...} }
-
重载签名不参与实际实现:
重载签名仅用于类型检查,不能包含实现代码。class Example {constructor(x: number); // ❌ 错误:重载签名不能有实现体constructor(x: number) { this.x = x; } // ✅ 正确:实现必须单独声明 }
3. 与可选参数的对比
构造函数重载 vs 可选参数:
方式 | 适用场景 | 示例 |
---|---|---|
重载 | 参数类型或数量差异较大,需要明确类型 | constructor(x: number); constructor(obj: { x: number }); |
可选参数 | 参数数量固定,部分参数可选 | constructor(x: number, y?: number); |
4. 常见应用场景
(1)创建工厂模式
通过不同参数创建不同类型的实例:
class Logger {// 重载签名constructor(source: string);constructor(config: { source: string; level: 'info' | 'error' });// 实现constructor(sourceOrConfig: string | { source: string; level: 'info' | 'error' }) {if (typeof sourceOrConfig === 'string') {// 处理字符串参数} else {// 处理对象参数}}
}
(2)处理不同数据格式
根据传入数据的不同格式初始化对象:
class User {id: number;name: string;// 重载签名constructor(data: { id: number; name: string });constructor(id: number, name: string);// 实现constructor(arg1: number | { id: number; name: string }, arg2?: string) {if (typeof arg1 === 'number') {this.id = arg1;this.name = arg2!;} else {this.id = arg1.id;this.name = arg1.name;}}
}
5. 注意事项
-
重载顺序很重要:
TypeScript 会按重载签名的顺序匹配参数,因此应将更具体的签名放在前面。class Example {constructor(x: number); // 更具体的签名constructor(x: any); // 更宽泛的签名constructor(x: any) { /* ... */ } }
-
避免过度重载:
过多的重载签名会使代码复杂,优先考虑使用可选参数或联合类型简化。// 简化前 constructor(x: number); constructor(x: string);// 简化后 constructor(x: number | string);
-
无法重载仅返回类型不同的构造函数:
构造函数重载必须基于参数差异,不能仅依赖返回类型。
7. 类的静态块(Static Blocks)
在 TypeScript 和 JavaScript 中,类的静态块(Static Blocks) 是 ES2022 引入的语法,用于在类定义时执行一次性的初始化逻辑。静态块内可以访问类的私有字段,且只会在类被加载时执行一次。
1. 静态块的特性
(1)仅执行一次
静态块在类被加载时自动执行,且只执行一次:
class App {static instances = 0;static {console.log("App class initialized");this.instances++;}
}// 输出: "App class initialized"
console.log(App.instances); // 1
console.log(App.instances); // 1(不会重复执行静态块)
(2)可访问私有字段
静态块可以直接访问类的私有静态字段
(3)支持多个静态块
类中可以定义多个静态块,它们会按顺序执行:
class Sequence {static #counter = 0;static {this.#counter = 100; // 初始化计数器}static {this.#counter++; // 递增计数器}static getNext() {return this.#counter++;}
}console.log(Sequence.getNext()); // 101
2. 与静态构造函数的对比
静态块 vs 静态属性初始化:
方式 | 执行时机 | 访问私有字段 | 执行顺序 |
---|---|---|---|
静态块 | 类加载时 | ✅ 可以 | 按定义顺序执行 |
静态属性初始化 | 类加载时 | ✅ 可以 | 按定义顺序执行 |
静态方法调用 | 显式调用时 | ✅ 可以 | 按需调用 |
3. 注意事项
- 不能直接返回值:
静态块没有返回值,其目的是执行副作用(如初始化静态属性)。 - 与类的实例无关:
静态块仅在类加载时执行,与实例化对象无关。即使类从未被实例化,静态块也会执行。