Agent X-Ray
RuntimeNotesAbout
Notes/代码工程/TypeScript 深度教程/第3章

第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
  • 框架要求类或装饰器元数据。

十二、常见误区

  1. any 编写通用函数,而不是泛型。
  2. 为每个对象创建类,照搬传统面向对象结构。
  3. 认为 interface 会在运行时存在。
  4. 认为 private 等价于 JavaScript #private
  5. 用重载掩盖一个设计混乱的函数。
  6. 让泛型参数只出现一次,未表达任何关系。
  7. 用宽泛索引签名隐藏拼写错误。
  8. 让一个配置函数接受多个布尔参数。

十三、实践练习

  1. 实现泛型 last<T>,空数组返回 undefined
  2. 为分页 API 建立 Page<T>ApiResult<T>
  3. 编写两个重载:传 Date 返回时间戳,传时间戳返回 Date
  4. 把一个包含多个布尔参数的函数改成配置对象。
  5. 分别用类和工厂函数实现计数器,比较状态与 API。

十四、总结

  1. 函数类型描述参数、返回值和调用关系。
  2. 重载适合稳定的输入—输出对应,简单情况优先联合类型。
  3. TypeScript 使用结构化类型,对象形状比声明名称更重要。
  4. interface 适合对象契约,type 适合更广泛的类型组合。
  5. 类是 JavaScript 运行时结构,接口和大部分类型只存在于编译期。
  6. 泛型的价值是保留类型之间的关系,而不是把所有东西写成 <T>

请继续阅读:第4章:类型建模。


原始资料引用


  • 第2章:语言基础
  • 第4章:类型建模

本章目录
一、函数签名就是调用契约二、可选参数、默认值与剩余参数三、函数重载四、this 与箭头函数五、对象的结构化类型六、接口与类型别名七、可选、只读与索引签名八、类九、类实现接口十、泛型:保留类型关系十一、对象创建与工厂函数十二、常见误区十三、实践练习十四、总结原始资料引用Related Documents
苏ICP备2025204887号-2