CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
TypeScript 核心/课时/函数类型
RawBlame
课时目录12 节

TypeScript 核心

  1. 01TypeScript 是什么
  2. 02基本类型
  3. 03函数类型
  4. 04接口与类型别名
  5. 05联合与交叉类型
  6. 06类型收窄
  7. 07泛型
  8. 08工具类型
  9. 09模块与 tsconfig
  10. 10React 组件 typing
  11. 11异步与 API 类型
  12. 12综合练习与 Next.js

函数类型

参数与返回值、可选参数、箭头函数 typing。

30 分钟

函数类型

这一课你将能做到

  1. 给函数声明、函数表达式、箭头函数标注完整的参数和返回值类型
  2. 正确使用可选参数、默认参数、剩余参数,并理解它们在类型层面的表现
  3. 用函数类型签名和重载描述「同一个函数多种调用方式」的场景

为什么要学

函数是 JavaScript 的一等公民,也是 TS 类型注解出现频率最高的地方。你写的每一个回调、map/filter 的入参、React 的事件处理函数,背后都是函数类型。

函数类型标注有个核心思想:函数的类型 = 输入(参数)+ 输出(返回值)。把这两个写准,TS 就能帮你检查所有调用点。函数参数是类型推断唯一「推不出来」的地方——所以函数参数必须手写注解,这是 TS 世界的一条铁律。

先建立图像

一个完整函数类型的解剖:

        参数名  参数类型        返回值类型
          │       │                │
function add(a: number, b: number): number {
  return a + b;
}
   │
函数名

整个函数的类型签名:(a: number, b: number) => number
                     参数列表          返回值

注意类型签名里的箭头 => 和箭头函数的 => 长得像但含义不同:前者出现在类型位置,表示「返回」;后者出现在值位置,是函数定义语法。

跟着做

新建 functions-demo.ts,每步代码可直接 tsc 编译运行。

步骤 1:函数声明与函数表达式的标注

// 函数声明:参数和返回值都标在函数头上
function multiply(a: number, b: number): number {
  return a * b;
}

// 函数表达式:两种方式
// 方式 A:只标函数本身(推荐,简洁)
const divide = function (a: number, b: number): number {
  return a / b;
};

// 方式 B:给变量标注完整函数类型(赋值给配置、接口时常用)
const subtract: (a: number, b: number) => number = function (a, b) {
  return a - b; // 参数类型从签名推断,不用重复写
};

console.log(multiply(3, 4), divide(10, 2), subtract(10, 3));
// 12 5 7

步骤 2:箭头函数与返回值推断

// 显式标注返回值
const square = (n: number): number => n * n;

// 返回值可推断时省略(推荐)
const double = (n: number) => n * 2; // TS 推断返回 number

// 无返回值
const log = (msg: string): void => {
  console.log(msg);
};

const nums = [1, 2, 3].map(double); // 回调参数 n 自动推断为 number
console.log(nums, square(5));       // [ 2, 4, 6 ] 25

经验:返回值类型大多数时候让 TS 推断;只有两种情况下手写——函数体复杂想给读者看契约、或要强制返回值符合某类型(写错能当场报错)。

步骤 3:可选参数与默认参数

// 可选参数:参数名后加 ?
function greet(name: string, greeting?: string): string {
  return `${greeting ?? "你好"},${name}`;
}

console.log(greet("小明"));          // 你好,小明
console.log(greet("小明", "早上好")); // 早上好,小明

// 默认参数:直接写默认值,类型自动推断,调用时可省略
function createUser(name: string, role: string = "viewer"): string {
  return `${name}(${role})`;
}

console.log(createUser("小红"));        // 小红(viewer)
console.log(createUser("管理员", "admin")); // 管理员(admin)

规则记住两条:可选参数的类型实际是 类型 | undefined;可选参数必须放在必选参数后面。

步骤 4:剩余参数

// 剩余参数:把不定数量的实参收集成数组,类型写成「元素类型[]」
function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

console.log(sum(1, 2));       // 3
console.log(sum(1, 2, 3, 4)); // 10

// 剩余参数前面可以有固定参数
function joinWith(separator: string, ...parts: string[]): string {
  return parts.join(separator);
}

console.log(joinWith("-", "2026", "10", "06")); // 2026-10-06

步骤 5:回调函数的类型

// 定义一个回调类型签名
type CompareFn = (a: number, b: number) => number;

function sortWith(list: number[], compare: CompareFn): number[] {
  return [...list].sort(compare);
}

const asc = sortWith([3, 1, 2], (a, b) => a - b);
const desc = sortWith([3, 1, 2], (a, b) => b - a);
console.log(asc, desc); // [ 1, 2, 3 ] [ 3, 2, 1 ]

type CompareFn = (a: number, b: number) => number 就是「函数类型别名」——这是实际项目里描述回调的标准姿势。

步骤 6:函数重载

当同一个函数有多种调用形态(参数个数或类型不同、返回值跟着变),用重载:

// 重载签名(只写类型,不写函数体)
function format(value: string): string;
function format(value: number, precision: number): string;

// 实现签名(兼容所有重载,函数体写这里)
function format(value: string | number, precision?: number): string {
  if (typeof value === "string") {
    return value.trim();
  }
  return value.toFixed(precision ?? 2);
}

console.log(format("  hello  "));  // "hello"
console.log(format(3.14159, 2));   // "3.14"
// console.log(format(true));      // 报错:没有匹配的重载

要点:重载签名在外,实现签名在内;调用时只能匹配重载签名,实现签名对外不可见。

步骤 7:综合实战——一个类型完整的事件发射器

type Handler = (payload: string) => void;

class Emitter {
  private handlers: Handler[] = [];

  on(handler: Handler): void {
    this.handlers.push(handler);
  }

  emit(payload: string): void {
    for (const handler of this.handlers) {
      handler(payload);
    }
  }
}

const emitter = new Emitter();
emitter.on((msg) => console.log("监听器1:", msg));
emitter.on((msg) => console.log("监听器2:", msg.toUpperCase()));

emitter.emit("hello ts");
// 监听器1: hello ts
// 监听器2: HELLO TS

讲清楚要点

  1. 参数必须注解,返回值可推断:这是 TS 函数标注的不对称规则。参数推不出来(没人告诉你调用方会传什么),返回值得出来(函数体写得明明白白)。
  2. 可选参数 ? 等价于 | undefined:greeting?: string 就是 greeting: string | undefined,且自动获得「可省略」能力。
  3. 默认参数自带可选属性:有默认值的参数调用时可省略,类型从默认值推断,不要再叠加 ?。
  4. 剩余参数永远是数组:...args: number[] 收集成一个真正的数组,放在参数列表最后。
  5. 重载是「签名堆叠」:写多个类型签名 + 一个兼容实现。能用一个联合类型签名解决的,就别上重载——重载只在「参数形态决定返回值类型」时必要。
  6. 函数类型也是值:函数可以赋给变量、当参数传、当返回值返回,类型签名 (a: T) => R 就是它的「形状描述」。

你可能会懵的地方

  • 箭头函数怎么写完整类型? (x: number) => number 是类型,const f = (x: number) => x * 2 是实现。想分开写:const f: (x: number) => number = (x) => x * 2,实现里的参数类型可以省略(从签名推断)。

  • 可选参数和 | undefined 完全一样吗? 不一样。a?: string 调用时可以不传;a: string | undefined 必须显式传 undefined。前者更常用。

  • 为什么回调函数里的参数可以不写类型? 上下文推断(contextual typing):TS 知道 arr.map 的回调收 number,就自动给回调参数标上。前提是这个位置的类型定义得好——这正是写库的人认真标注函数类型的意义。

  • 重载和联合类型参数选哪个? 参数类型不同但返回值类型不变 → 用联合类型一个签名搞定;返回值类型随参数形态变化 → 才用重载。

  • void 返回的函数能 return 值 吗? 严格说 return someValue 会报错,但 return 空返回可以。特殊例外:把一个返回值的函数赋给 void 类型的回调是允许的(比如 forEach 的回调),TS 故意放宽,避免连锁报错。

错法对照

错法问题正解
function f(a, b) { ... }隐式 any,strict 下直接报错参数逐个标注类型
可选参数放必选前面 (a?: string, b: number)编译错误必选在前,可选在后
默认参数还加 ?:f(a?: number = 1)语法错误,两者重复表达「可省略」f(a: number = 1)
给回调硬标 any:arr.map((x: any) => ...)丢掉上下文推断红利省略参数类型让 TS 推
重载实现签名写得太窄重载签名与实现不兼容报错实现签名用联合类型覆盖所有分支

卡点急救

  1. 报错 Parameter 'x' implicitly has an 'any' type:strict 模式的 noImplicitAny 生效了,给参数补上类型注解。
  2. 可选参数调用时传 undefined 报错:检查函数定义是 a?: T 还是 a: T;后者必须传值。改成可选参数或 T | undefined。
  3. 重载调用报 No overload matches this call:调用形态没匹配上任何重载签名。检查参数类型和个数;或确认实现签名是否真的处理了这种情况。
  4. 回调里 this 丢失:用了普通函数当回调导致 this 漂移。改用箭头函数,或在签名里声明 this: void。

面试常问

  • 函数类型注解的完整写法? (参数名: 类型, ...) => 返回值类型。可赋给变量:const f: (a: number) => number = ...。参数类型不可省,返回值可被推断。

  • 可选参数和默认参数的区别? 可选参数 ?:不传时是 undefined,函数体内要处理;默认参数:不传时用默认值,函数体内拿到的永远是有值的。默认参数类型从默认值推断。

  • 什么是函数重载?TS 和 Java 的重载有何不同? TS 重载是多个类型签名 + 一个实现,仅在编译期生效,运行时只有一个函数;Java 重载是多个真实方法共存。TS 重载的目的是给不同调用形态配不同的返回类型。

  • void 和 undefined 作为返回类型的区别? void 表示「不期待使用返回值」,赋给它的函数即使 return 了值也被接受(回调场景);undefined 要求函数必须显式 return undefined。描述函数返回用 void。

自测三问

  1. 为什么函数参数必须手写类型而返回值通常不用?
  2. function f(a?: string) 和 function f(a: string | undefined) 调用时有什么区别?
  3. 什么场景必须用函数重载,什么场景用联合类型参数就够了?

小结一句话

函数类型的核心是「输入输出契约」:参数必标、返回值可推断、可选/默认/剩余参数处理变化、重载描述多形态——契约写清楚,所有调用点都有编译器站岗。

延伸

  • Handbook: More on Functions
  • 下一课:接口与类型别名——interface 和 type 怎么选、可选/只读属性、索引签名、继承与交叉。
加载本节练习…
← 上一课基本类型下一课 →接口与类型别名