云计算百科
云计算领域专业知识百科平台

TypeScript学习-第4章:函数类型

TypeScript学习-第4章:函数类型

各位前端工友们,上一章咱们搞定了复合类型,相当于把TS世界里的“零件”和“组装件”都摸清了。而函数,就是操控这些“组装件”干活的“执行者”——不管是处理数据、发起请求,还是渲染页面,本质都是靠函数来落地逻辑。

但JS里的函数就像“野生执行者”,参数传啥、返回啥全凭自觉,很容易出现“传错参数类型”“返回值不符合预期”的bug。这一章咱们就给函数“立规矩”,吃透函数类型的精准约束,让每一个“执行者”都按规则干活,既灵活又不跑偏。

一、函数的类型标注:给执行者定“工作手册”

函数的类型标注核心就两件事:约束“输入”(参数)和“输出”(返回值)。相当于给执行者一本工作手册,明确规定“要接收什么类型的任务(参数)”“完成后要返回什么类型的结果(返回值)”。咱们分普通函数和箭头函数两种场景来讲,覆盖日常开发全部用法。

1. 普通函数:完整标注格式

普通函数的标注语法很清晰:参数列表里给每个参数加类型,函数名后加:和返回值类型(无返回值用void)。

// 函数:接收两个number类型参数,返回number类型结果
function add(a: number, b: number): number {
return a + b;
}

// 正确调用:参数类型、数量完全匹配
const result = add(10, 20); // result被推导为number类型

// 错误示例:参数类型不匹配
// add(10, "20"); // 报错:预期number,实际string

// 无返回值函数:返回值类型标为void
function printLog(msg: string): void {
console.log(msg);
// 可省略return,或return undefined
// return; // 合法
// return undefined; // 合法
// return 10; // 报错:不能返回number,预期void
}

避坑点:void表示“无返回值”,不是“返回undefined”——虽然函数默认返回undefined,但标注为void后,就不能返回任何有意义的值,强行返回会报错。

2. 箭头函数:简化式标注

箭头函数的标注逻辑和普通函数一致,只是语法更简洁,适合匿名函数、回调函数场景。有两种常见写法,按需选择:

// 写法1:直接在参数和箭头后标注返回值类型
const multiply = (a: number, b: number): number => a * b;

// 写法2:给箭头函数整体标注类型(结合类型别名更优雅,后续会讲)
type MultiplyFn = (a: number, b: number) => number;
const multiply2: MultiplyFn = (a, b) => a * b;

// 无返回值箭头函数
const printMsg = (msg: string): void => console.log(msg);

小技巧:箭头函数在作为回调时,标注会更简洁,比如数组的map方法:

const numbers: number[] = [1, 2, 3];
// map回调:参数n是number,返回string,推导result为string[]
const numberStrs = numbers.map((n: number): string => `数字:${n}`);

二、可选参数与默认参数:给执行者留“灵活空间”

实际开发中,很多函数的参数不是必填的(比如筛选列表的默认条件),这时候就需要可选参数和默认参数,给执行者留足灵活度,但同时要保持类型约束。

1. 可选参数:用?标记“非必填任务”

在参数名后加?,表示该参数可选——调用函数时可以传,也可以不传。但有个硬性规则:可选参数必须放在必选参数后面。

// 可选参数b:number类型,可传可不传
function calculate(a: number, b?: number): number {
// 可选参数可能为undefined,需先判断再使用
return b ? a + b : a;
}

// 正确调用方式
calculate(10); // 只传必选参数,b为undefined
calculate(10, 20); // 传必选+可选参数

// 错误示例:可选参数放必选参数前面
// function wrongCalculate(b?: number, a: number): number { … } // 报错

避坑点:可选参数的类型会自动推导为“对应类型 | undefined”,所以必须先判断是否存在,再使用,避免运行时错误。

2. 默认参数:给“可选任务”设“默认值”

如果可选参数有默认值,可以直接在参数列表里赋值,此时参数会自动被视为可选参数,无需再加?。同时,默认参数的类型会自动推导,也可以手动标注。

// 默认参数c:默认值为10,类型自动推导为number
function addWithDefault(a: number, b: number, c = 10): number {
return a + b + c;
}

// 正确调用方式
addWithDefault(1, 2); // c使用默认值10,结果13
addWithDefault(1, 2, 20); // 覆盖默认值,结果23

// 手动标注默认参数类型(适合默认值为复杂类型时)
function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}`;
}

小技巧:默认参数可以放在必选参数中间,但调用时如果要跳过默认参数,需要传undefined(不推荐,易混淆,建议默认参数都放最后)。

三、剩余参数:让执行者“灵活接收批量任务”

有些场景下,函数的参数数量不固定(比如求和函数,可传任意个数字),这时候就需要剩余参数——用…标记,接收所有剩余的参数,并存为一个数组。

// 剩余参数args:接收所有后续参数,类型为number[]
function sum(args: number[]): number {
return args.reduce((total, curr) => total + curr, 0);
}

// 正确调用:传任意个number类型参数
sum(1); // 结果1
sum(1, 2, 3); // 结果6
sum(10, 20, 30, 40); // 结果100

// 剩余参数结合必选参数
function joinStr(separator: string, strs: string[]): string {
return strs.join(separator);
}
joinStr("-", "a", "b", "c"); // 结果"a-b-c"

避坑点:剩余参数必须是函数的最后一个参数,且类型只能是数组类型(比如number[]、string[]),不能是单个类型。

四、函数重载:给执行者配“多套工作手册”

有些函数需要处理不同类型/数量的参数,返回不同类型的结果(比如“获取用户信息”,传ID返回单个用户,传数组ID返回用户列表)。这时候就需要函数重载——为同一个函数定义多个“类型签名”,让函数根据入参自动匹配对应的约束规则。

函数重载的核心是“先声明,后实现”:先定义多个类型签名,再写一个兼容所有签名的函数实现。

// 1. 定义函数重载签名(多套工作手册)
function getUser(id: number): { id: number; name: string }; // 传number,返回单个用户
function getUser(ids: number[]): { id: number; name: string }[]; // 传number[],返回用户列表

// 2. 实现函数(兼容所有签名,需处理所有场景)
function getUser(idOrIds: number | number[]): { id: number; name: string } | { id: number; name: string }[] {
if (typeof idOrIds === "number") {
// 处理单个ID场景
return { id: idOrIds, name: `用户${idOrIds}` };
} else {
// 处理ID数组场景
return idOrIds.map(id => ({ id, name: `用户${id}` }));
}
}

// 正确调用:自动匹配对应签名
const user = getUser(1001); // 匹配第一个签名,返回单个用户
const userList = getUser([1001, 1002]); // 匹配第二个签名,返回用户列表

// 错误示例:无对应签名
// getUser("1001"); // 报错:无接收string类型的签名

避坑点:函数实现的参数类型和返回值类型,必须兼容所有重载签名,不能遗漏任何一种场景。同时,重载签名要精准区分,避免模糊冲突(比如两个签名参数数量相同、类型兼容,TS无法区分)。

五、类型别名:给函数类型“起别名”,提升复用性

如果多个函数的类型签名相同(比如多个回调函数都是“接收number,返回string”),反复写完整类型会很冗余。这时候可以用type给函数类型起别名,实现类型复用。

// 定义函数类型别名:接收number,返回string
type NumberToStringFn = (num: number) => string;

// 复用别名定义函数
const formatNum: NumberToStringFn = (num) => `数字:${num}`;
const convertNum: NumberToStringFn = (num) => num.toString();

// 别名也可用于函数参数(比如回调函数参数)
function processNumber(num: number, callback: NumberToStringFn): string {
return callback(num);
}

// 调用:回调函数自动匹配别名类型
processNumber(10, (num) => `处理后:${num}`); // 结果"处理后:10"

小技巧:函数类型别名可以结合复合类型、泛型(后续章节)使用,比如定义“接收用户对象,返回布尔值”的别名,适配复杂业务场景。

六、实践:回调函数的类型约束(高频场景)

回调函数是前端开发的高频场景(异步请求、事件监听、数组方法等),用TS约束回调类型,能避免“回调参数乱传、返回值不符合预期”的问题。咱们结合两个核心场景实战。

场景1:异步回调(如定时器、请求回调)

// 定义异步函数,接收回调参数
type AsyncCallback = (result: string) => void; // 回调:接收string,无返回值
function fetchData(callback: AsyncCallback) {
// 模拟异步请求
setTimeout(() => {
callback("请求成功,返回数据"); // 回调参数必须是string,否则报错
}, 1000);
}

// 调用:回调函数参数自动匹配类型
fetchData((res) => {
console.log(res); // res被推导为string,有代码提示
});

场景2:事件回调(如DOM事件)

DOM事件回调有自带的类型(如MouseEvent、KeyboardEvent),无需手动定义,直接复用TS内置类型即可。

// 获取按钮元素(用非空断言!,排除null)
const btn = document.getElementById("btn")!;

// 点击事件回调:参数e类型为MouseEvent
btn.addEventListener("click", (e: MouseEvent) => {
console.log("点击位置:", e.clientX, e.clientY); // e有完整的事件属性提示
});

// 键盘事件回调:参数e类型为KeyboardEvent
document.addEventListener("keydown", (e: KeyboardEvent) => {
console.log("按下按键:", e.key);
});

避坑点:DOM元素获取可能返回null,用!非空断言(确认元素存在),或先判断元素是否存在,再绑定事件,避免类型错误。

七、本章小结:函数类型的核心是“精准约束输入输出”

这一章咱们吃透了函数类型的全场景约束,核心逻辑可以总结为:函数类型标注的本质是“锁定输入输出的规则”——可选参数、默认参数、剩余参数负责“灵活适配输入场景”,函数重载负责“多场景差异化约束”,类型别名负责“提升类型复用性”。

新手容易踩的坑:一是可选参数放错位置;二是忽略可选参数的undefined判断;三是函数重载实现不兼容所有签名;四是回调函数滥用any。这些都需要通过多写实战代码来磨合,养成“先定规则,再写逻辑”的习惯。

下一章咱们要解锁TS的“结构化约束神器”——接口(Interface),学会用接口统一复杂对象、函数的类型规则,让代码更规范、更易维护。咱们下一章见!

赞(0)
未经允许不得转载:网硕互联帮助中心 » TypeScript学习-第4章:函数类型
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!