第3章:函数(Functions)
一、导读
1.1 本章学习目标
第3章讲解 TypeScript 中函数的类型化方式。函数是 JavaScript 的核心,TypeScript 为函数提供了丰富的类型标注能力。
本章的核心学习目标包括:
- 掌握函数类型的声明方式:参数类型、返回类型、可选参数、默认参数
- 理解函数重载(Overloading):TypeScript 如何实现类型安全的重载
- 掌握 `this` 类型的声明:如何在 TypeScript 中正确标注
this - 理解回调函数的类型化:如何处理异步回调和事件处理器
- 学会使用函数类型构建灵活的 API
1.2 为什么本章如此重要
JavaScript 的函数极其灵活——它可以作为参数传递、作为返回值、绑定不同的 this。这种灵活性在大型项目中容易出错。TypeScript 的函数类型系统让你能够精确地描述函数的签名,在编译期捕获参数类型错误、this 绑定错误等常见问题。
1.3 核心问题引导
TypeScript 的函数重载与 Java/C# 的重载有什么本质区别?
如何正确标注回调函数的类型?void 返回类型意味着什么?
this 参数如何在 TypeScript 中显式声明?
函数类型 (a: string) => number 和 { (a: string): number } 有什么区别?
二、核心概念详解
2.1 函数类型基础
// 完整函数类型
type AddFn = (a: number, b: number) => number;
const add: AddFn = (a, b) => a + b;
// 可选参数和默认参数
function greet(name: string, greeting?: string): string {
return `${greeting ?? "Hello"}, ${name}!`;
}
greet("Alice"); // "Hello, Alice!"
greet("Alice", "Hi"); // "Hi, Alice!"
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}2.2 函数重载
TypeScript 的函数重载通过声明多个函数签名来实现:
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;
function format(value: string | number | Date): string {
if (typeof value === "string") return value;
if (typeof value === "number") return value.toFixed(2);
return value.toISOString();
}
format("hello"); // string
format(3.14); // string
format(new Date()); // string
// format(true); // 错误!没有匹配的签名2.3 this 类型
interface UIElement {
addClickListener(onClick: (this: void, e: Event) => void): void;
}
class Handler {
info: string;
onClickGood = (e: Event) => { // 箭头函数,this 绑定到实例
this.info = e.type;
};
onClickBad(this: Handler, e: Event) { // 显式声明 this 类型
this.info = e.type;
}
}2.4 回调函数类型
// void 返回类型的回调
function fetchData(callback: (data: string) => void) {
callback("result");
}
// void 作为返回类型意味着"返回值会被忽略"
fetchData((data) => data.length); // OK! 返回值被忽略
// Promise 和 async 函数
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// 函数类型属性
interface EventEmitter {
on(event: "click", handler: (x: number, y: number) => void): void;
on(event: "keydown", handler: (key: string) => void): void;
on(event: string, handler: (...args: any[]) => void): void;
}三、实战案例
3.1 案例一:类型安全的事件系统
type EventMap = {
click: { x: number; y: number };
keydown: { key: string; code: string };
resize: { width: number; height: number };
};
class TypedEventEmitter<T extends Record<string, any>> {
private handlers = new Map<string, Set<Function>>();
on<K extends keyof T>(event: K, handler: (data: T[K]) => void) {
if (!this.handlers.has(event as string)) {
this.handlers.set(event as string, new Set());
}
this.handlers.get(event as string)!.add(handler);
}
emit<K extends keyof T>(event: K, data: T[K]) {
const handlers = this.handlers.get(event as string);
handlers?.forEach(h => h(data));
}
}
const emitter = new TypedEventEmitter<EventMap>();
emitter.on("click", ({ x, y }) => console.log(`Click at ${x}, ${y}`));
emitter.on("keydown", ({ key }) => console.log(`Key: ${key}`));
emitter.emit("click", { x: 100, y: 200 });3.2 案例二:类型安全的管道函数
type Fn<A, B> = (input: A) => B;
function pipe<A>(value: A): A;
function pipe<A, B>(value: A, fn1: Fn<A, B>): B;
function pipe<A, B, C>(value: A, fn1: Fn<A, B>, fn2: Fn<B, C>): C;
function pipe<A, B, C, D>(value: A, fn1: Fn<A, B>, fn2: Fn<B, C>, fn3: Fn<C, D>): D;
function pipe(value: any, ...fns: Fn<any, any>[]) {
return fns.reduce((acc, fn) => fn(acc), value);
}
const result = pipe(
"42",
(s: string) => parseInt(s, 10),
(n: number) => n * 2,
(n: number) => `Result: ${n}`
);
console.log(result); // "Result: 84"四、本章小结
函数类型:参数类型、返回类型、可选参数、默认参数、剩余参数。
函数重载:通过多个函数签名实现类型安全的重载。
this 类型:显式声明 this 参数类型,箭头函数自动绑定 this。
回调函数:void 返回类型表示"返回值被忽略"。
五、思考与练习
TypeScript 的函数重载与 Java 的重载有什么本质区别?
箭头函数和普通函数在 this 绑定上有什么区别?TypeScript 如何帮助你避免 this 错误?
编写一个类型安全的 memoize 函数,保留原函数的类型签名。