Skip to content
类与面向对象

类与面向对象

本章涵盖 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 classnew 抽象类不能被实例化,只能被继承后使用
静态方法访问实例成员 static 方法中 this 指向类本身,不能访问实例属性