第3章:函数与对象 —— 签名、重载、接口、类与泛型
约 4 分钟 · 更新于 2026-09-01
第3章:函数与对象 —— 签名、重载、接口、类与泛型
TypeScript 的类型系统围绕 JavaScript 最常见的两种结构展开:函数和对象。本章学习如何为调用关系建模,并在接口、类和泛型之间做出合适选择。
一、函数签名就是调用契约
ts
function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}
函数类型可以单独命名:
ts
type Formatter = (value: number, currency: string) => string;
const formatPrice: Formatter = (value, currency) =>
`${currency} ${value.toFixed(2)}`;
参数名不要求一致,位置和类型才决定兼容性。
二、可选参数、默认值与剩余参数
ts
function greet(name: string, title?: string): string {
return title ? `${title} ${name}` : name;
}
function request(url: string, timeout = 3000): Promise<Response> {
return fetch(url, { signal: AbortSignal.timeout(timeout) });
}
function sum(...values: number[]): number {
return values.reduce((total, value) => total + value, 0);
}
可选参数通常放在必选参数后。参数组合复杂时使用配置对象,避免多个布尔值和可选位置参数。
ts
interface RequestOptions {
timeout?: number;
retries?: number;
cache?: boolean;
}
三、函数重载
重载适合“输入形式不同,返回类型存在稳定对应关系”的 API:
ts
function parse(value: string): number;
function parse(value: number): string;
function parse(value: string | number): string | number {
return typeof value === "string" ? Number(value) : String(value);
}
调用者只能看到重载签名,最后一个实现签名负责覆盖所有情况。
不要为简单联合类型滥用重载:
ts
function length(value: string | readonly unknown[]): number {
return value.length;
}
四、this 与箭头函数
箭头函数捕获词法 this:
ts
class Counter {
count = 0;
increment = () => {
this.count += 1;
};
}
普通方法的 this 取决于调用方式:
ts
const counter = new Counter();
const fn = counter.increment;
fn(); // 箭头属性仍指向实例
库 API 还可以显式声明 this 参数,它只参与类型检查,不出现在运行时参数中。
五、对象的结构化类型
TypeScript 主要采用结构化类型:只要形状兼容,就可以赋值。
ts
interface Named {
name: string;
}
const employee = { id: 1, name: "Ada", department: "R&D" };
const named: Named = employee;
这也常被称为“鸭子类型”的静态版本。它提高组合能力,但意味着接口不是名义身份标记。
六、接口与类型别名
ts
interface User {
id: number;
name: string;
}
type UserId = string | number;
type Point = readonly [number, number];
两者都能描述对象:
ts
interface ApiResult<T> {
data: T;
requestId: string;
}
type ApiError = {
code: string;
message: string;
};
选择建议:
- 对象公共契约、可扩展声明:优先 interface。
- 联合、交叉、元组、条件类型:使用 type。
- 团队一致性比争论语法偏好更重要。
七、可选、只读与索引签名
ts
interface User {
readonly id: number;
name: string;
email?: string;
}
readonly 是编译期约束,不会自动执行 Object.freeze。
索引签名:
ts
interface PriceMap {
[route: string]: number;
}
如果键集合有限,应使用映射类型而不是无限字符串键:
ts
type Cabin = "economy" | "business";
type CabinPrice = Record<Cabin, number>;
八、类
ts
class Account {
constructor(
public readonly id: string,
private balance: number
) {}
deposit(amount: number): void {
if (amount <= 0) throw new Error("金额必须为正数");
this.balance += amount;
}
getBalance(): number {
return this.balance;
}
}
- public:公开访问,默认值。
- private:TypeScript 私有成员。
- protected:类及子类可访问。
- readonly:初始化后不能通过类型系统重新赋值。
JavaScript 运行时真正的私有字段使用 #balance,它与 TypeScript 的 private 不是同一机制。
九、类实现接口
ts
interface Serializable {
serialize(): string;
}
class UserRecord implements Serializable {
constructor(public id: number, public name: string) {}
serialize(): string {
return JSON.stringify({ id: this.id, name: this.name });
}
}
implements 检查实例侧契约,不会自动生成实现,也不会改变运行时原型链。
十、泛型:保留类型关系
错误做法是用 any 抹掉输入和输出关系:
ts
function first(values: any[]): any {
return values[0];
}
泛型保留关系:
ts
function first<T>(values: readonly T[]): T | undefined {
return values[0];
}
泛型接口
ts
interface Page<T> {
items: T[];
page: number;
total: number;
}
泛型约束
ts
function getId<T extends { id: string | number }>(value: T): T["id"] {
return value.id;
}
多类型参数
ts
function mapValues<T, U>(values: readonly T[], mapper: (value: T) => U): U[] {
return values.map(mapper);
}
十一、对象创建与工厂函数
不是所有对象都需要类。纯数据更适合接口或类型别名,行为简单时工厂函数也更轻量:
ts
interface Counter {
readonly value: number;
increment(): Counter;
}
function createCounter(value = 0): Counter {
return {
value,
increment: () => createCounter(value + 1)
};
}
使用类的典型理由:
- 需要封装可变状态。
- 需要实例方法和生命周期。
- 需要继承或运行时 instanceof。
- 框架要求类或装饰器元数据。
十二、常见误区
- 用 any 编写通用函数,而不是泛型。
- 为每个对象创建类,照搬传统面向对象结构。
- 认为 interface 会在运行时存在。
- 认为 private 等价于 JavaScript #private。
- 用重载掩盖一个设计混乱的函数。
- 让泛型参数只出现一次,未表达任何关系。
- 用宽泛索引签名隐藏拼写错误。
- 让一个配置函数接受多个布尔参数。
十三、实践练习
- 实现泛型 last<T>,空数组返回 undefined。
- 为分页 API 建立 Page<T> 和 ApiResult<T>。
- 编写两个重载:传 Date 返回时间戳,传时间戳返回 Date。
- 把一个包含多个布尔参数的函数改成配置对象。
- 分别用类和工厂函数实现计数器,比较状态与 API。
十四、总结
- 函数类型描述参数、返回值和调用关系。
- 重载适合稳定的输入—输出对应,简单情况优先联合类型。
- TypeScript 使用结构化类型,对象形状比声明名称更重要。
- interface 适合对象契约,type 适合更广泛的类型组合。
- 类是 JavaScript 运行时结构,接口和大部分类型只存在于编译期。
- 泛型的价值是保留类型之间的关系,而不是把所有东西写成 <T>。
请继续阅读:第4章:类型建模。
原始资料引用