函数的艺术:参数、返回值与重载

目录

第 1 节 概述

上一篇文章我们学了怎么描述"数据"的形状。但程序不光有数据,还有行为——而行为的基本单元就是函数。

JavaScript 的函数灵活多变:参数可多可少、返回值可有可无、同一个函数名可以有不同的调用方式。

TypeScript 给这些灵活性加上了类型约束,让函数的调用方式清晰可辨。

第 2 节 函数类型表达式

最简单的写法——给参数和返回值加类型:

1
2
3
function add(x: number, y: number): number {
  return x + y
}

箭头函数也一样:

1
2
3
const add = (x: number, y: number): number => {
  return x + y
}

类型推断可以帮你省掉一些标注:

1
2
3
4
const add = (x: number, y: number) => {
  return x + y
}
// 返回值被推断为 number,不需要写

2.1 独立的函数类型

函数本身也有类型。可以用 typeinterface 描述:

1
2
3
4
type BinaryFn = (a: number, b: number) => number

const add: BinaryFn = (x, y) => x + y
const multiply: BinaryFn = (x, y) => x * y

这在回调函数中很实用:

1
2
3
4
5
type Callback = (error: Error | null, data: string) => void

function fetchData(url: string, cb: Callback) {
  // ...
}

第 3 节 可选参数与默认值

参数可以用 ? 标记为可选:

1
2
3
4
5
6
7
8
9
function greet(name: string, greeting?: string) {
  if (greeting) {
    return `${greeting}${name}`
  }
  return `你好,${name}`
}

greet("张三")           // ✅ greeting 可选
greet("张三", "Hello")  // ✅

可选参数必须放在必选参数之后

1
function wrong(greeting?: string, name: string) {}  // ❌ 可选参数不能在前

更常用的方式是给参数默认值

1
2
3
4
5
6
function greet(name: string, greeting = "你好") {
  return `${greeting}${name}`
}

greet("张三")               // "你好,张三"
greet("张三", "Hello")      // "Hello,张三"

带默认值的参数自动成为可选参数,且不需要写类型注解——TypeScript 会根据默认值推断。

第 4 节 Rest 参数

当参数数量不确定时,用 ... 收集剩余参数:

1
2
3
4
5
6
function sum(...numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0)
}

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

Rest 参数可以是数组或元组类型:

1
2
3
4
5
6
// 第一个参数是名字,后面是多个分数
function record(name: string, ...scores: number[]) {
  console.log(name, scores)
}

record("张三", 85, 92, 78)

用元组约束 Rest 参数的精确结构:

1
2
3
4
5
6
function logEvent(event: string, ...details: [number, string]) {
  console.log(event, details[0], details[1])
}

// 等价于:logEvent(event: string, id: number, message: string)
logEvent("登录", 1, "用户登录成功")

第 5 节 void 返回值

void 表示函数没有返回值,或者我们不关心它的返回值:

1
2
3
4
5
6
7
8
9
function log(message: string): void {
  console.log(message)
}

function forEach<T>(items: T[], callback: (item: T) => void) {
  for (const item of items) {
    callback(item)
  }
}

voidundefined 不同:

1
2
3
4
5
6
7
function fn1(): void {
  // 可以没有 return,也可以 return undefined
}

function fn2(): undefined {
  return undefined  // 必须显式 return undefined
}
技巧

回调函数的返回值类型用 void,表示"我不在乎你返回什么"。即使回调实际返回了值,赋给 void 类型也不会报错。

第 6 节 函数重载

JavaScript 中,同一个函数名可以处理不同数量和类型的参数。TypeScript 通过重载签名来精确描述:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 重载签名 —— 定义所有可能的调用方式
function len(value: string): number
function len(value: unknown[]): number

// 实现签名 —— 实际逻辑
function len(value: string | unknown[]): number {
  return value.length
}

len("hello")   // ✅ 匹配第一个重载
len([1, 2, 3]) // ✅ 匹配第二个重载
len(42)        // ❌ 没有匹配的重载

更实际的例子——根据参数类型做不同处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 重载签名
function createUser(name: string): User
function createUser(name: string, age: number): User
function createUser(name: string, age: number, email: string): User

// 实现签名 —— 参数范围要覆盖所有重载
function createUser(name: string, age?: number, email?: string): User {
  return {
    id: generateId(),
    name,
    age: age ?? 18,
    email: email ?? `${name}@example.com`,
  }
}

// 三种调用方式都合法
createUser("张三")
createUser("张三", 20)
createUser("张三", 20, "z@example.com")
注意

重载签名只是类型声明,不包含实现。实现签名不能直接调用,它的参数类型必须能覆盖所有重载。

第 7 节 this 类型

JavaScript 函数中的 this 指向取决于调用方式。TypeScript 允许在函数的第一个参数中声明 this 的类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
interface User {
  name: string
  admin: boolean
}

function checkAdmin(this: User) {
  return this.admin
}

const user = { name: "张三", admin: true }
checkAdmin.call(user)  // ✅ this 指向 User

checkAdmin()           // ❌ this 没有绑定

在事件处理器中很实用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
interface Button {
  text: string
  onClick(this: HTMLElement, event: MouseEvent): void
}

const button: Button = {
  text: "提交",
  onClick(this, event) {
    // this 被限制为 HTMLElement
    this.innerText = "已点击"
  },
}

函数是程序的行为骨架。现在你能给参数、返回值、重载甚至 this 加类型标注了。

但有一类特殊函数还没讲——。类既是数据模板,也是行为容器。下一篇我们看 TypeScript 中的类。

目录