Skip to content
函数与泛型

函数与泛型

本章涵盖 TypeScript 函数声明、参数类型、函数重载,以及泛型函数、泛型约束、泛型接口等核心泛型特性,帮助你掌握 TS 类型系统中与函数相关的高级能力。


函数类型声明

TypeScript 中可以为函数参数和返回值添加类型注解。箭头函数在参数列表和函数体之间必须有 =>

// 箭头函数
const add = (a: number, b: number): number => {
    return a + b
}

// 具名函数
function multiply(a: number, b: number): number {
    return a * b
}

// 无返回值
function log(message: string): void {
    console.log(message)
}

函数类型别名

可以用 type 定义函数类型别名,将类型与实现分离,便于复用和作为回调参数传递:

type MathOp = (a: number, b: number) => number

const add: MathOp = (a, b) => a + b       // ✅ 实参类型自动推断
const multiply: MathOp = (a, b) => a * b

// 作为回调参数
function calculate(op: MathOp, x: number, y: number): number {
    return op(x, y)
}

参数

可选参数

在参数名后加 ? 表示可选参数。可选参数必须放在必选参数之后,未传入时值为 undefined

function greet(name: string, title?: string): string {
    return title ? `${title} ${name}` : name
}

greet("Willow")              // ✅ "Willow"
greet("Willow", "Dr.")       // ✅ "Dr. Willow"

🚨 陷阱:可选参数不可与默认值参数同时使用 ? — 有默认值的参数本身就是可选的,加 ? 会产生冲突。可选参数必须位于参数列表末尾(除 rest 参数外)。

默认参数

默认值参数自动获得可选特性,但会覆盖 undefined(传入的 undefined 会触发默认值)。

// ✅ 正确写法
const greeting = (name: string, age: number = 18): string => {
    return `I am ${name}, and age is ${age} years old.`
}

greeting("Willow")           // I am Willow, and age is 18 years old.

// ❌ 错误:不能同时使用 ? 和默认值
// function bad(param?: string = "default") {}

Rest 参数

Rest 参数必须是参数列表的最后一个,类型标注为数组:

function sum(a: number, b: number, ...rest: number[]): number {
    return rest.reduce((s, n) => s + n, a + b)
}

sum(1, 2)               // 3
sum(1, 2, 3, 4, 5)      // 15

函数重载

当函数的参数类型或数量不同导致返回值类型也不同时,使用函数重载为每种调用方式提供精确的类型定义。

重载签名

重载签名是对函数多种调用方式的类型声明,只定义参数签名与返回值签名,没有函数体实现:

function getId(id: string): string
function getId(id: number): number
  • 重载签名是调用方看到的接口,IDE 跳转和代码提示都会定位到重载签名
  • TS 从第一个重载签名开始尝试匹配,向下查找第一个兼容的签名

实现签名

实现签名提供统一的函数体,参数和返回值必须使用能涵盖所有重载签名的宽泛类型:

// 重载签名(两个,无函数体)
function getId(id: string): string
function getId(id: number): number

// 实现签名(一个,宽泛类型)
function getId(id: unknown): unknown {
    if (typeof id === "string") {
        return id
    }
    return id
}

getId("willow")  // 返回 string 类型 ✅
getId(42)        // 返回 number 类型 ✅

🚨 陷阱:实现签名使用宽泛类型(如 unknown),调用方只能看到重载签名。重载签名顺序很重要 — 具体在前、宽泛在后。实现签名本身对外部不可见。

如果实现签名类型不够宽泛,会导致类型错误:

// ❌ 错误:实现签名返回值是 string,无法匹配第二个重载签名(返回 number)
function getId(id: string): string
function getId(id: number): number
// function getId(id: unknown): string { ... }  // ❌ 类型错误

💡 最佳实践:重载签名按从具体到宽泛的顺序排列,最精确的匹配放在最前面。


泛型函数

泛型允许函数在定义时不指定具体类型,而在调用时由传入参数推断或显式指定。这使得函数可以处理多种类型,同时保持类型安全。

function identity<T>(arg: T): T {
    return arg
}

// 类型推断:TS 根据传入值自动推断 T
const result = identity("hello")   // const result: "hello"
const count = identity(42)         // const count: 42

// 显式指定类型参数
const num = identity<number>(42)   // const num: number
const str = identity<string>("hi") // const str: string

T 是类型变量,命名惯例使用单大写字母。常见命名:

字母 含义
T Type(通用类型)
K Key(键类型)
V Value(值类型)
E Element(元素类型)
R Return(返回值类型)

泛型约束

使用 extends 关键字限制类型参数必须满足某个结构或接口,从而在函数体内安全地访问特定属性:

function longest<T extends { length: number }>(a: T, b: T): T {
    return a.length >= b.length ? a : b
}

longest("hello", "world")        // ✅ string 有 length
longest([1, 2], [3, 4, 5])       // ✅ array 有 length
// longest(1, 2)                 // ❌ Error: number 没有 length

使用 keyof 约束

keyof 获取对象类型的键的联合类型,常用于安全地访问对象属性:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key]
}

const user = { name: "Willow", age: 28, email: "w@example.com" }

getProperty(user, "name")   // ✅ 返回 string
getProperty(user, "age")    // ✅ 返回 number
// getProperty(user, "phone") // ❌ Error: "phone" 不是 user 的键

泛型默认值

可以为泛型参数设置默认类型,调用方不指定时使用默认值:

function createBox<T = string>(): { contents: T } {
    return { contents: undefined as unknown as T }
}

const stringBox = createBox()          // { contents: string }
const numBox = createBox<number>()     // { contents: number }

多个类型参数

泛型函数可以接受多个类型参数,实现灵活的依赖关系:

function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second]
}

const p1 = pair("hello", 42)          // [string, number]
const p2 = pair(true, [1, 2, 3])      // [boolean, number[]]

// 常见组合:K(键)、V(值)
function getOrDefault<K, V>(map: Map<K, V>, key: K, defaultVal: V): V {
    return map.has(key) ? map.get(key)! : defaultVal
}

泛型接口与泛型类(简介)

泛型同样适用于接口和类,实现高复用的容器和数据结构。详细内容参见后续章节。

// 泛型接口
interface Repository<T> {
    getById(id: string): T
    save(entity: T): void
    list(): T[]
}

// 泛型类
class Stack<T> {
    private items: T[] = []

    push(item: T): void { this.items.push(item) }
    pop(): T | undefined { return this.items.pop() }
    peek(): T | undefined { return this.items[this.items.length - 1] }
}

const numStack = new Stack<number>()
numStack.push(1)
numStack.push(2)
console.log(numStack.pop())  // 2

this 参数

TypeScript 允许将 this 作为函数的第一个参数进行类型声明,该参数是编译时标注,不会出现在编译后的 JavaScript 中:

function handleClick(this: HTMLButtonElement, event: MouseEvent): void {
    console.log(this.textContent)  // this 类型安全
}

const btn = document.querySelector("button")!
// btn.addEventListener("click", handleClick)             // ✅ this 正确
// btn.addEventListener("click", e => handleClick(e))     // ❌ this 丢失

💡 最佳实践:使用 this 参数避免回调中的 this 丢失问题。它是纯编译时的类型辅助,不会生成任何运行时代码,也不计入实际参数个数。

如果 this 类型与调用上下文不匹配,TS 会在编译期报错:

function getName(this: { name: string }): string {
    return this.name
}

const obj = { name: "Willow", getName }
obj.getName()  // ✅ this 匹配

const standalone = obj.getName
// standalone()  // ❌ Error: 'this' context is not assignable

调用签名与构造签名

调用签名(Call Signature)

用于描述一个可作为函数调用的对象,同时该对象还可能带有额外属性:

interface Callable {
    (x: number): string
    description: string
}

function createCallable(): Callable {
    const fn = (x: number): string => `Value: ${x}`
    fn.description = "A callable number-to-string converter"
    return fn
}

const c = createCallable()
c(42)            // "Value: 42"
c.description    // "A callable number-to-string converter"

构造签名(Construct Signature)

描述可以用 new 调用的类型,常见于工厂模式或依赖注入:

interface Constructable<T> {
    new (name: string): T
}

class Person {
    constructor(public name: string) {}
}

function factory<T>(ctor: Constructable<T>, name: string): T {
    return new ctor(name)
}

const p = factory(Person, "Willow")  // Person 实例

常见陷阱汇总

陷阱 说明 正确做法
voidundefined 混淆 void 表示忽略返回值,但运行时可能返回 undefinedvoid 类型变量只能赋值为 undefined 区分编译时语义和运行时行为,不要依赖 void 返回值的具体内容
Rest 参数位置错误 Rest 参数必须放在参数列表最后,且只能有一个 function fn(a: number, ...rest: number[]): void {}
重载签名顺序不当 宽泛签名在前会吞掉后续具体签名的匹配 具体签名在前,宽泛签名在后
泛型约束过紧 使用 extends 约束了不必要的属性,导致函数适用范围变窄 只约束函数体内实际访问的属性,使用 extends { length: number } 而非 extends string
类型参数推断混淆 多个类型参数时,只部分显式指定会导致推断失败 要么全部推断,要么全部显式指定;或考虑拆分函数