类与面向对象
本章涵盖 TypeScript 类的声明、构造方法、访问修饰符、只读属性、访问器、静态成员、抽象类、接口实现、ECMAScript 私有字段、泛型类以及装饰器等面向对象核心特性。
类声明
使用 class 关键字声明一个类,通过 new 关键字创建实例,this 指向当前实例对象。
class Point {
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
distance(): number {
return Math.sqrt(this.x ** 2 + this.y ** 2)
}
}
const p = new Point(3, 4)
console.log(p.distance()) // 5
构造方法
构造方法使用 constructor 关键字声明(而不是类名)。TypeScript 只允许一个构造方法,不支持 Java 风格的多构造器重载。需要不同参数组合时,使用可选参数或联合类型。
🚨 陷阱:声明多个构造方法会导致编译错误。可选参数、带默认值的参数是 TypeScript 中模拟多签名的唯一方式。
推荐的简便写法——参数属性(Parameter Properties):直接在构造器参数前加修饰符,自动声明并赋值成员:
class User {
constructor(
private name: string,
private age: number = 18
) {}
public toString(): string {
return `[Name] ${this.name} [Age] ${this.age}`
}
}
const u = new User("willow", 28)
console.log(u.toString()) // [Name] willow [Age] 28
构造函数也可以重载声明(只声明签名,不实现),但实现体仍只有一个:
class Point {
x: number
y: number
constructor(x: number, y: number)
constructor(xy: string)
constructor(x: number | string, y?: number) {
if (typeof x === "string") {
const parts = x.split(",")
this.x = parseInt(parts[0])
this.y = parseInt(parts[1])
} else {
this.x = x
this.y = y!
}
}
}💡 最佳实践:优先使用参数属性简写。当参数超过 3 个时,考虑传入一个配置对象(
options: { ... })。
访问修饰符
TypeScript 提供三种访问修饰符,控制成员在不同上下文中的可见性:
| 修饰符 | 本类 | 子类 | 外部 |
|---|---|---|---|
public |
✅ | ✅ | ✅ |
protected |
✅ | ✅ | ❌ |
private |
✅ | ❌ | ❌ |
默认修饰符为 public,可省略不写。
子类重写的权限规则:子类可以将父类方法的访问修饰符改得 更宽松,但不能改得 更严格。
class Parent {
protected method(): void {
console.log("parent")
}
}
class Child extends Parent {
// ✅ OK: protected → public(更宽松)
public method(): void {
console.log("child")
}
// ❌ Error: protected → private(更严格)
// private method(): void {}
}readonly
readonly 将属性标记为只读,只能在 声明时 或 构造函数内 赋值,之后不可修改。
class Config {
readonly apiUrl: string = "https://api.example.com"
constructor(url?: string) {
if (url) this.apiUrl = url
}
}
const config = new Config()
// config.apiUrl = "other" // ❌ Error: Cannot assign to 'apiUrl' because it is a read-only property.
get 与 set(访问器)
通过 get / set 为私有字段提供受控的读写入口,可在内部加入验证逻辑。
class User {
private _name: string
constructor(name: string) {
this._name = name
}
get name(): string {
return this._name
}
set name(value: string) {
if (!value) throw new Error("Name required")
this._name = value
}
}
const user = new User("John")
user.name = "Jane"
console.log(user.name) // Jane
💡 最佳实践:当属性的读取或写入需要附加逻辑(校验、转换、通知)时使用访问器;纯粹的数据存储用普通属性即可。
static
static 成员属于类本身,不属于实例。所有实例共享同一份静态成员(内存中只存在一份)。
class MyMath {
static readonly PI = 3.14159
static circleArea(r: number): number {
return this.PI * r * r
}
static sum(a: number, b: number): number {
return a + b
}
}
console.log(MyMath.sum(1, 2)) // 3
console.log(MyMath.circleArea(2)) // 12.56636
🚨 陷阱:静态方法内部只能访问
static成员,不能直接访问实例成员(实例成员需要new之后才存在)。
abstract
抽象类不能直接实例化,只能被继承。抽象方法只声明签名,不提供实现体。
abstract class Mover {
abstract move(x: number, y: number): void
// 抽象类可以有具体方法
logMove(x: number, y: number): void {
console.log(`Moving to (${x}, ${y})`)
}
}
class Person extends Mover {
// ✅ 必须实现抽象方法
move(x: number, y: number): void {
this.logMove(x, y)
console.log("Person is walking")
}
}
const p = new Person()
p.move(1, 2)
// const m = new Mover() // ❌ Error: Cannot create an instance of an abstract class.
🚨 陷阱:抽象方法只能在抽象类中声明,且不能有实现体(只声明签名)。
implements(实现接口)
implements 强制类满足一个或多个接口的契约。一个类可以实现多个接口。
interface Printable {
print(): void
}
interface Storable {
save(path: string): void
}
class Document implements Printable, Storable {
print(): void {
console.log("printing...")
}
save(path: string): void {
console.log(`saving to ${path}`)
}
}🔬 深入原理:
implements只在编译时检查——确保类的实例方法签名与接口匹配,不会影响运行时行为。
ECMAScript 私有字段(#)
TypeScript 的 private 只在编译时生效,运行时仍可通过类型断言访问。ES2022 引入的 # 前缀字段提供 真正的运行时私有性(hard private),外部代码无法访问。
| 特性 | private |
#field |
|---|---|---|
| 检测时机 | 仅编译时 | 编译时 + 运行时 |
| 运行时访问 | 可通过类型断言绕过 | 完全不可访问 |
| 标准来源 | TypeScript 特有 | ECMAScript 标准 |
class BankAccount {
#balance: number = 0
deposit(amount: number): void {
this.#balance += amount
}
getBalance(): number {
return this.#balance
}
}
const account = new BankAccount()
account.deposit(100)
console.log(account.getBalance()) // 100
// account.#balance // ❌ SyntaxError: Private field '#balance' must be declared in an enclosing class
💡 最佳实践:需要真正运行时的私有性时用
#,否则用private即可。#字段无法被子类访问,也无法通过Object.keys()等 API 枚举。
泛型类
类可以带类型参数,实现类型安全的通用容器。
class Box<T> {
contents: T
constructor(value: T) {
this.contents = value
}
unwrap(): T {
return this.contents
}
}
const stringBox = new Box("hello") // Box<string>
const numberBox = new Box(42) // Box<number>
console.log(stringBox.unwrap()) // "hello"
this 参数
在函数或方法的参数列表中,this 可作为 假参数 用于类型注解,限定调用时 this 的上下文类型(编译后会被移除)。
class Button {
label: string = "Submit"
click(this: Button): void {
console.log(`Clicked: ${this.label}`)
}
}
const btn = new Button()
btn.click() // ✅ OK
const detached = btn.click
// detached() // ❌ Error: The 'this' context of type 'void' is not assignable to method's 'this' of type 'Button'.
静态初始化块
static {} 块在类首次加载时执行一次,用于复杂的静态成员初始化逻辑(ES2022)。
class Config {
static settings: Record<string, string>
static {
// 在类加载时执行一次
this.settings = { env: "production", region: "us-east-1" }
}
}
console.log(Config.settings.env) // "production"
🔬 深入原理:静态初始化块可以替代复杂的静态属性初始化表达式,支持
try/catch、条件判断等语句,比直接赋值更灵活。
装饰器概览
TypeScript 5.0 支持 Stage 3 TC39 装饰器(基于 Symbol.metadata)。用于在类、方法、字段上声明元编程逻辑。
function log(value: any, ctx: ClassMethodDecoratorContext) {
const methodName = String(ctx.name)
return function (this: any, ...args: any[]) {
console.log(`[${methodName}] called with`, args)
return value.call(this, ...args)
}
}
class Service {
@log
getData(id: number): string {
return `data-${id}`
}
}
const svc = new Service()
svc.getData(5) // [getData] called with [5]
💡 最佳实践:新项目使用 TC39 装饰器(无需
experimentalDecorators标志)。旧版experimentalDecorators装饰器仍可工作,但属于遗留方案。
类表达式
类可以像函数一样作为表达式赋值给变量,支持匿名写法:
const Point = class {
constructor(public x: number, public y: number) {}
}
const p = new Point(1, 2)
console.log(p.x, p.y) // 1 2
类表达式常用于工厂函数或单次使用的场景。
常见陷阱汇总
| 陷阱 | 说明 |
|---|---|
多个 constructor |
TypeScript 只允许一个构造器实现体,用可选参数或用函数签名的重载声明替代 |
private ≠ 运行时私有 |
private 仅是编译时约束,运行时可通过 (obj as any).privateField 绕过;需要硬私有用 # |
| 访问修饰符收紧 | 子类重写父类方法时,修饰符只能放宽不能收紧(protected 不能重写为 private) |
| 抽象方法有实现体 | 抽象方法只能声明签名,不允许有 {} 函数体 |
abstract class 被 new |
抽象类不能被实例化,只能被继承后使用 |
| 静态方法访问实例成员 | static 方法中 this 指向类本身,不能访问实例属性 |