第66课:函数类型与重载——参数类型、返回值类型、this 类型、函数重载
函数是 JavaScript 中的一等公民,也是 TypeScript 类型系统的重点关注对象。TypeScript 提供了丰富的语法来描述函数的参数类型、返回值类型、**this 上下文类型,以及通过函数重载(Function Overloads)** 来表达一个函数可以接受不同参数组合并返回不同结果的能力。本节课将系统讲解函数类型注解的完整语法、可选参数与默认参数的类型交互、剩余参数的类型声明、以及如何通过重载签名来精确描述复杂的函数契约。
1. 函数类型注解
1.1 参数类型与返回值类型
在 TypeScript 中,每个参数都可以添加类型注解,返回值类型写在参数列表的括号之后。
1 | function add(a: number, b: number): number { |
返回值类型可以省略(让 TypeScript 推断),但显式注解能带来两个好处:
- 作为文档清晰表达函数意图。
- 防止函数内部意外返回错误类型的值(编译器会检查
return语句)。
1 | // ❌ 函数内部可能意外返回 string,但显式标注 :number 会在编译时报错 |
1.2 可选参数
使用 ? 标记参数为可选的。可选参数必须放在必选参数之后。
1 | function greet(name: string, greeting?: string): string { |
可选参数在调用时可以不传值,此时参数值为 undefined。注意与使用默认值的区别——默认参数在未传值时也有值(不是 undefined),因此可以为默认参数添加更宽松的类型。
1.3 默认参数值
默认参数可以放在任何位置,TypeScript 会自动推断其类型,也可以显式注解。与 JavaScript 一致,只有传入 undefined 时默认值才会被使用。
1 | function createUser(name: string, role: string = 'user'): { name: string; role: string } { |
1.4 剩余参数
剩余参数收集所有额外的实参到一个数组中,类型注解应使用数组类型。
1 | function sum(...numbers: number[]): number { |
剩余参数也可以是元组类型,从而精确控制剩余参数的数量和类型:
1 | function logPair(message: string, ...args: [number, string]): void { |
1.5 函数类型表达式
可以使用箭头语法定义一个函数类型,并赋给类型别名或直接用于变量声明。
1 | type MathOperation = (a: number, b: number) => number; |
参数名不必与类型别名中的一致(如 x、y 替代 a、b),但类型必须兼容。
2. this 类型
在 TypeScript 中,可以通过函数签名的第一个参数声明 this 的类型。这不是真正的参数,而是一种编译时约束,告诉 TypeScript 该函数期望在什么样的上下文中被调用。
1 | interface Card { |
this 参数在编译后会被完全删除,不生成任何 JavaScript 代码。它是纯粹的静态约束。
2.1 this 参数的实际应用
在回调模式中,通过声明 this 类型确保函数不会被错误调用:
1 | class Button { |
3. 函数重载(Function Overloads)
函数重载允许你为一个函数定义多个调用签名,每种签名对应不同的参数组合和返回值类型。TypeScript 会选择第一个匹配的签名来检查调用是否合法。
3.1 基本语法
重载由多个重载签名(函数头,没有函数体)和一个实现签名(有函数体)组成。实现签名必须兼容所有重载签名。
1 | // 重载签名 |
关键规则:
- 重载签名是给外部调用者看的(类型检查的依据)。
- 实现签名是给内部实现看的(但不参与外部类型检查)。
- 实现签名的参数类型必须是联合类型,覆盖所有重载签名的参数类型。
3.2 不同参数数量与返回值类型
重载可以表达一个函数接受不同数量、不同类型的参数并返回不同类型的值。
1 | // 重载签名 |
3.3 字面量类型重载
当参数为特定字面量值时,可以返回不同的类型。这是重载最强大的应用之一。
1 | function getConfig(key: 'url'): string; |
当调用 getConfig('url') 时,TypeScript 匹配到第一个重载签名,返回类型被精确推断为 string,而非 string | number。
4. 可调用的对象(Callable Object)
有些对象既可以作为函数调用,又拥有自己的属性。可以使用调用签名和普通属性组合来定义这种类型。
1 | interface Counter { |
5. 最佳实践:函数类型设计原则
- 明确参数意图:所有参数都应有类型注解,即使是简单类型。这充当函数的文档。
- 返回值类型酌情显式声明:简单、清晰的函数可以依赖推断;公共 API、复杂函数推荐显式声明。
- 谨慎使用重载:如果可以通过联合类型和条件类型实现相同效果,优先使用更简单的方案。重载应仅用于参数组合导致返回值类型显著不同的场景。
this类型用于回调安全:在类方法或需要特定上下文的对象方法中,使用this参数防止丢失上下文。- **剩余参数优于
arguments**:类型安全且是真正的数组。
课后练习
一、概念自测(选择题 / 填空题)
(单选) 在 TypeScript 函数重载中,以下哪个签名会被用于类型检查?
A. 只有实现签名
B. 只有重载签名
C. 重载签名和实现签名都会被检查
D. 实现签名优先(单选) 以下关于函数可选参数的说法,正确的是?
A. 可选参数可以放在必选参数之前。
B. 可选参数在函数内部的值是null。
C. 可选参数必须放在必选参数之后。
D. 可选参数不能有默认值。(填空) 为了声明一个函数期望的
this类型,应在函数签名的参数列表的______位置添加this: Type。(多选) 以下哪些是 TypeScript 中定义函数类型的合法方式?
A.type Fn = (x: number) => string
B.interface Fn { (x: number): string }
C.type Fn = { x: number }: string
D.const fn: { (x: number): string } = (x) => x.toString()
二、AI 编程任务:编写面向 AI 的提示词
场景:你需要编写一个 TypeScript 函数 format(value, formatType),根据 formatType 的不同值返回不同类型的结果。要求如下:
- 如果
formatType是'uppercase',value必须是string,返回string(全大写)。 - 如果
formatType是'number',value可以是string或number,返回number(将字符串转为数字或直接返回数字)。若无法转为数字(isNaN),返回null。 - 如果
formatType是'boolean',value可以是string、number或boolean,返回boolean。'true'或1转为true,其他转为false。 - 使用函数重载精确描述三种调用签名。
- 实现签名中使用
switch处理逻辑。 - 添加 JSDoc 注释说明每种重载的行为。
任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 TypeScript 代码。提示词中需明确指定重载签名、实现签名、参数类型和返回类型的对应关系。
三、Agent 模式下的提示词示例
你是一个资深前端开发 Agent。请创建一个 TypeScript 文件
src/format.ts,实现一个通过重载精确描述的类型安全格式化函数。要求:
- 定义三个重载签名:
format(value: string, formatType: 'uppercase'): stringformat(value: string | number, formatType: 'number'): number | nullformat(value: string | number | boolean, formatType: 'boolean'): boolean- 实现签名:
function format(value: string | number | boolean, formatType: 'uppercase' | 'number' | 'boolean'): string | number | null | boolean。内部用switch (formatType)实现对应逻辑:uppercase返回(value as string).toUpperCase();number将值用Number()转换,若isNaN返回null;boolean将值转为布尔逻辑。- 为每个重载签名添加 JSDoc 注释描述其行为。
- 在文件末尾添加测试代码(使用
console.log调用三种重载,并注释说明类型正确)。- 使用 ESM 导出函数,确保代码可直接编译。完成后输出完整文件内容。
四、面试真题与参考答案
题目(滴滴前端面试题):
请解释 TypeScript 中函数重载的工作原理。它和联合类型参数有什么不同?在什么情况下应该使用函数重载而不是联合类型?
参考答案:
1. 重载的工作原理:TypeScript 的函数重载由多个重载签名(仅声明,无函数体)和一个实现签名(包含具体实现,但对外部不可见)组成。当调用函数时,编译器依次检查重载签名列表,找到第一个匹配的签名来验证参数,并根据该签名的返回值类型推断结果类型。实现签名不在外部类型检查的考虑范围内。
2. 与联合类型的区别:
- 联合类型:参数和返回值均使用联合类型(如
value: string | number,返回值string | number)。调用方无法通过参数值精确地缩窄返回类型——即使传入string,返回类型仍是string | number。 - 函数重载:根据参数的具体类型或字面量值,返回精确对应的类型。调用
format('hello', 'uppercase')时,返回类型被精确推断为string,而非联合类型。
3. 选择原则:
- 使用联合类型:当不同参数类型导致的行为和返回类型相同时,或者返回类型之间的差异较小时。联合类型更简洁,维护成本更低。
- 使用函数重载:当参数组合与返回值之间存在精确的对应关系,且调用方需要精确的返回类型推断时。典型场景包括:
- 不同参数数量/类型对应不同返回值(如
createDate)。 - 特定字面量参数值导致截然不同的返回值类型(如
getConfig)。 - API 设计需要为调用方提供最佳的 IDE 智能提示和类型安全保障。
- 不同参数数量/类型对应不同返回值(如
课后练习答案
一、概念自测答案
B
- 解析:只有重载签名参与外部的类型检查,实现签名仅供内部使用,不参与调用时的类型检查。
C
- 解析:可选参数必须放在必选参数之后,否则编译器无法正确推断实参的对应关系。可选参数未传值时值为
undefined,可以有默认值。
- 解析:可选参数必须放在必选参数之后,否则编译器无法正确推断实参的对应关系。可选参数未传值时值为
第一个
- 解析:
function fn(this: MyType, otherParam: string)——this必须是第一个参数。
- 解析:
A、B、D
- 解析:A 是函数类型表达式;B 是调用签名接口;D 是行内调用签名类型。C 语法错误。
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请编写一个 TypeScript 函数format,使用函数重载精确描述不同 formatType 下的行为。要求:
- 重载签名1:
format(value: string, formatType: 'uppercase'): string(返回 value.toUpperCase())- 重载签名2:
format(value: string | number, formatType: 'number'): number | null(Number() 转换,NaN 返回 null)- 重载签名3:
format(value: string | number | boolean, formatType: 'boolean'): boolean(转为布尔值)- 实现签名用
switch (formatType)实现,添加 JSDoc 注释。- 提供三个调用示例注释。直接输出完整代码。”