第 3 章:函数与类¶
学习时间:3 小时 | 难度:⭐⭐ 初级 | 前置知识:第 2 章
本章概览¶
本章深入 TypeScript 的函数系统和面向对象编程。从函数重载到类的访问控制,再到 TypeScript 5.x 标准化的装饰器,你将掌握构建健壮 TypeScript 应用的 OOP 工具箱。
学习目标:
- 掌握函数重载、可选参数、默认参数、rest 参数
- 理解
this类型标注与函数类型的协变/逆变 - 掌握类的访问修饰符:
public、private、protected、readonly - 理解抽象类与接口的实现区别
- 掌握 TypeScript 5.x 标准化装饰器(ES2022 Decorators)
- 理解
satisfies运算符和const类型参数
3.1 函数类型与重载¶
TypeScript
// ── 函数类型的完整写法 ──────────────────────────────
// 写法 1:类型别名
type Comparator<T> = (a: T, b: T) => number;
// 写法 2:接口(可调用签名)
interface Middleware {
(req: Request, res: Response, next: () => void): void;
}
// ── 可选参数与默认参数 ──────────────────────────────
function createUser(
name: string,
email: string,
role: "admin" | "user" = "user", // 默认值(同时隐式设为可选)
age?: number // 可选参数(必须在必填参数后面)
): User {
return { id: Math.random(), name, email, role, age };
}
interface User {
id: number;
name: string;
email: string;
role: string;
age?: number;
}
// ── Rest 参数 ────────────────────────────────────────
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// ── 函数重载:同一函数名,不同参数类型 ─────────────
// 先写重载签名(只声明,不实现)
function parse(input: string): number;
function parse(input: number): string;
function parse(input: string[]): number[];
// 再写实现签名(必须兼容所有重载签名)
function parse(input: string | number | string[]): number | string | number[] {
if (typeof input === "string") {
return parseInt(input, 10); // string → number
}
if (typeof input === "number") {
return input.toString(); // number → string
}
return input.map((s) => parseInt(s, 10)); // string[] → number[]
}
// 调用时 TypeScript 根据参数类型选择正确的重载
const num = parse("42"); // 类型:number
const str = parse(42); // 类型:string
const nums = parse(["1", "2"]); // 类型:number[]
// ── 泛型函数 ─────────────────────────────────────────
// 函数级泛型(不需要类级别时优先用函数泛型)
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
function zip<A, B>(arrA: A[], arrB: B[]): [A, B][] {
return arrA.map((a, i) => [a, arrB[i]] as [A, B]);
}
const zipped = zip([1, 2, 3], ["a", "b", "c"]);
// zipped: [number, string][]
// ── this 类型 ────────────────────────────────────────
interface Counter {
count: number;
increment(): this; // 返回 this 支持链式调用
decrement(): this;
reset(): this;
}
class NumberCounter implements Counter {
count = 0;
increment(): this {
this.count++;
return this;
}
decrement(): this {
this.count--;
return this;
}
reset(): this {
this.count = 0;
return this;
}
}
const c = new NumberCounter();
c.increment().increment().increment().decrement(); // count = 2(链式调用)
3.2 类(Class)¶
TypeScript
// ── 完整的 TypeScript 类示例 ────────────────────────
class BankAccount {
// 属性声明与访问修饰符
readonly accountId: string; // 只读:初始化后不可改
private balance: number; // 私有:只有类内部访问
protected owner: string; // 受保护:子类可访问
public currency: string; // 公开(默认)
// 静态属性
static interestRate = 0.05;
// 构造函数简写(参数前加修饰符直接变属性)
constructor(
owner: string,
private readonly bankName: string, // private readonly 直接声明
initialBalance: number = 0,
) {
this.accountId = crypto.randomUUID(); // 生成唯一 ID
this.owner = owner;
this.balance = initialBalance;
this.currency = "CNY";
}
// Getter / Setter
get currentBalance(): number {
return this.balance;
}
set deposit(amount: number) {
if (amount <= 0) throw new Error("Deposit must be positive");
this.balance += amount;
}
// 实例方法
withdraw(amount: number): void {
if (amount > this.balance) {
throw new Error("Insufficient funds");
}
this.balance -= amount;
}
// 静态方法
static calculateInterest(principal: number, years: number): number {
return principal * this.interestRate * years;
}
// toString 用于调试
toString(): string {
return `[${this.bankName}] ${this.owner}: ${this.currency} ${this.balance}`;
}
}
const account = new BankAccount("Alice", "招商银行", 10000);
account.deposit = 5000; // 触发 setter
console.log(account.currentBalance); // 15000(getter)
account.withdraw(3000);
// account.balance = 0; // ❌ 错误:private 不可外部访问
// ── 继承 ─────────────────────────────────────────────
class SavingsAccount extends BankAccount {
private savingsRate: number;
constructor(owner: string, bankName: string, initialBalance: number, savingsRate: number) {
super(owner, bankName, initialBalance); // 必须先调用 super()
this.savingsRate = savingsRate;
}
// 重写方法
override withdraw(amount: number): void {
// override 关键字:确保父类有此方法(防止拼写错误)
console.log(`Savings penalty applied for ${this.owner}`);
super.withdraw(amount); // 调用父类方法
}
addInterest(): void {
const interest = this.currentBalance * this.savingsRate;
this.deposit = interest; // 触发父类 setter
}
}
3.3 抽象类与接口实现¶
TypeScript
// 抽象类:不能直接实例化,定义子类必须实现的契约
abstract class Animal {
abstract makeSound(): string; // 抽象方法:子类必须实现
abstract get name(): string; // 抽象 getter
// 非抽象方法:可以直接继承
describe(): string {
return `I am ${this.name} and I say: ${this.makeSound()}`;
}
sleep(): void {
console.log(`${this.name} is sleeping...`);
}
}
class Dog extends Animal {
get name(): string { return "Dog"; }
makeSound(): string { return "Woof!"; }
}
class Cat extends Animal {
get name(): string { return "Cat"; }
makeSound(): string { return "Meow!"; }
}
// const animal = new Animal(); // ❌ 错误:抽象类不能实例化
const dog = new Dog();
console.log(dog.describe()); // "I am Dog and I say: Woof!"
// 接口实现(implements)
interface Serializable {
serialize(): string;
}
interface Validatable {
validate(): boolean;
}
// 一个类可以实现多个接口
class FormData implements Serializable, Validatable {
constructor(
private email: string,
private name: string
) {}
serialize(): string {
return JSON.stringify({ email: this.email, name: this.name });
}
validate(): boolean {
return this.email.includes("@") && this.name.length >= 2;
}
}
3.4 TypeScript 5.x 标准化装饰器¶
TypeScript 5.0 实现了 TC39 Stage 3 装饰器提案(与旧版 experimentalDecorators 不同):
TypeScript
// 需要在 tsconfig.json 中,target >= ES2022(新装饰器不需要 experimentalDecorators)
// ── 类装饰器 ─────────────────────────────────────────
function singleton<T extends { new(...args: unknown[]): object }>(
BaseClass: T,
_ctx: ClassDecoratorContext
) {
let instance: InstanceType<T>;
const newClass = class extends BaseClass {
constructor(...args: unknown[]) {
if (instance) return instance; // 如果已有实例,直接返回
super(...args);
instance = this as InstanceType<T>;
}
};
return newClass as T;
}
@singleton
class DatabaseConnection {
private static connectionCount = 0;
constructor(public readonly url: string) {
DatabaseConnection.connectionCount++;
console.log(`Connection #${DatabaseConnection.connectionCount} created`);
}
}
const db1 = new DatabaseConnection("postgres://...");
const db2 = new DatabaseConnection("postgres://...");
console.log(db1 === db2); // true(单例模式)
// ── 方法装饰器 ───────────────────────────────────────
function log(target: unknown, context: ClassMethodDecoratorContext) {
const methodName = String(context.name);
return function (this: unknown, ...args: unknown[]) {
console.log(`[${methodName}] called with:`, args);
const result = (target as Function).apply(this, args);
console.log(`[${methodName}] returned:`, result);
return result;
};
}
function memoize(_target: unknown, context: ClassMethodDecoratorContext) {
const cache = new Map<string, unknown>();
return function (this: unknown, ...args: unknown[]) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = (_target as Function).apply(this, args);
cache.set(key, result);
return result;
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
@memoize
@log
fibonacci(n: number): number {
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
// ── 字段装饰器 ───────────────────────────────────────
function nonNegative(target: undefined, context: ClassFieldDecoratorContext) {
return function (this: unknown, value: number): number {
if (value < 0) throw new RangeError(`${String(context.name)} cannot be negative`);
return value;
};
}
class Product {
@nonNegative // 确保价格不为负数
price: number = 0;
constructor(price: number) {
this.price = price; // 触发装饰器验证
}
}
3.5 TypeScript 5.x 新特性速查¶
satisfies 运算符(TS 4.9+)¶
TypeScript
// 问题:as 断言会丢失精确类型,不加断言又无利用约束
// satisfies 两全其美:既检查约束,又保留精确类型
type Colors = Record<string, string | [number, number, number]>;
// ❌ 旧写法:id 丢失颜色精确类型
const palette1: Colors = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
};
// palette1.red 类型是 string | [number, number, number],无法用 map
// ✅ satisfies:检查是否满足 Colors 约束,但保留字面量类型
const palette2 = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
} satisfies Colors;
// palette2.red 类型仍然是 [number, number, number](精确元组!)
const [r, g, b] = palette2.red; // ✅ OK:TS 知道是三元素元组
// palette2.green 类型是 string
const upper = palette2.green.toUpperCase(); // ✅ OK:TS 知道是 string
const 类型参数(TS 5.0+)¶
TypeScript
// 问题:泛型类型推断有时过于宽泛
function route<T extends string>(path: T) {
return { path, handler: () => {} };
}
const r1 = route("/users");
// r1.path 的类型是 string(太宽了)
// ✅ const 类型参数:推断为字面量类型
function routeConst<const T extends string>(path: T) {
return { path, handler: () => {} };
}
const r2 = routeConst("/users");
// r2.path 的类型是 "/users"(字面量!)
// 对数组特别有用
function createTuple<const T extends readonly unknown[]>(items: T) {
return items;
}
const t = createTuple([1, "hello", true]);
// t 的类型是 readonly [1, "hello", true](元组!而非 (number | string | boolean)[])
using 声明(TS 5.2+ / ES2025)¶
TypeScript
// 使用 Symbol.dispose 自动资源管理(类似 Python with / C# using)
class DatabaseConnection {
constructor(public url: string) {
console.log(`Connected to ${url}`);
}
query(sql: string) {
return `Results for: ${sql}`;
}
[Symbol.dispose]() {
console.log(`Disconnected from ${this.url}`);
}
}
{
using conn = new DatabaseConnection("postgres://localhost/mydb");
const result = conn.query("SELECT * FROM users");
console.log(result);
// 代码块结束时自动调用 conn[Symbol.dispose]()
}
// 输出:
// Connected to postgres://localhost/mydb
// Results for: SELECT * FROM users
// Disconnected from postgres://localhost/mydb
🏋️ 本章练习¶
- 类设计:实现一个
EventEmitter<Events>泛型类,使用参数为事件名映射到处理函数类型(如{ click: (x: number, y: number) => void }) - 装饰器实战:写
@validate方法装饰器,在调用前验证参数非空 - satisfies 练习:为路由配置对象使用
satisfies确保类型安全并保留精确路径类型
📌 本章小结¶
| 概念 | 关键点 |
|---|---|
| 函数重载 | 先写签名,再写实现;实现签名不对外暴露 |
| 访问修饰符 | private/protected/public/readonly + 构造函数简写 |
| 抽象类 | 定义子类契约,可包含实现;abstract 不可实例化 |
| 装饰器 | TS 5.0 标准化,无需 experimentalDecorators |
satisfies | 约束检查 + 保留精确类型(两全其美) |
const 泛型 | 推断字面量/元组而非宽泛类型 |
using | 自动资源管理(dispose pattern) |
下一章:高级类型编程——泛型约束、条件类型、映射类型、infer、模板字面量类型。
TypeScript 5.8 · 2025