TypeScript学习-第4章:函数类型
各位前端工友们,上一章咱们搞定了复合类型,相当于把TS世界里的“零件”和“组装件”都摸清了。而函数,就是操控这些“组装件”干活的“执行者”——不管是处理数据、发起请求,还是渲染页面,本质都是靠函数来落地逻辑。
但JS里的函数就像“野生执行者”,参数传啥、返回啥全凭自觉,很容易出现“传错参数类型”“返回值不符合预期”的bug。这一章咱们就给函数“立规矩”,吃透函数类型的精准约束,让每一个“执行者”都按规则干活,既灵活又不跑偏。
一、函数的类型标注:给执行者定“工作手册”
函数的类型标注核心就两件事:约束“输入”(参数)和“输出”(返回值)。相当于给执行者一本工作手册,明确规定“要接收什么类型的任务(参数)”“完成后要返回什么类型的结果(返回值)”。咱们分普通函数和箭头函数两种场景来讲,覆盖日常开发全部用法。
1. 普通函数:完整标注格式
普通函数的标注语法很清晰:参数列表里给每个参数加类型,函数名后加:和返回值类型(无返回值用void)。
// 函数:接收两个number类型参数,返回number类型结果functionadd(a:number,b:number):number{returna+b;}// 正确调用:参数类型、数量完全匹配constresult=add(10,20);// result被推导为number类型// 错误示例:参数类型不匹配// add(10, "20"); // 报错:预期number,实际string// 无返回值函数:返回值类型标为voidfunctionprintLog(msg:string):void{console.log(msg);// 可省略return,或return undefined// return; // 合法// return undefined; // 合法// return 10; // 报错:不能返回number,预期void}避坑点:void表示“无返回值”,不是“返回undefined”——虽然函数默认返回undefined,但标注为void后,就不能返回任何有意义的值,强行返回会报错。
2. 箭头函数:简化式标注
箭头函数的标注逻辑和普通函数一致,只是语法更简洁,适合匿名函数、回调函数场景。有两种常见写法,按需选择:
// 写法1:直接在参数和箭头后标注返回值类型constmultiply=(a:number,b:number):number=>a*b;// 写法2:给箭头函数整体标注类型(结合类型别名更优雅,后续会讲)typeMultiplyFn=(a:number,b:number)=>number;constmultiply2:MultiplyFn=(a,b)=>a*b;// 无返回值箭头函数constprintMsg=(msg:string):void=>console.log(msg);小技巧:箭头函数在作为回调时,标注会更简洁,比如数组的map方法:
constnumbers:number[]=[1,2,3];// map回调:参数n是number,返回string,推导result为string[]constnumberStrs=numbers.map((n:number):string=>`数字:${n}`);二、可选参数与默认参数:给执行者留“灵活空间”
实际开发中,很多函数的参数不是必填的(比如筛选列表的默认条件),这时候就需要可选参数和默认参数,给执行者留足灵活度,但同时要保持类型约束。
1. 可选参数:用?标记“非必填任务”
在参数名后加?,表示该参数可选——调用函数时可以传,也可以不传。但有个硬性规则:可选参数必须放在必选参数后面。
// 可选参数b:number类型,可传可不传functioncalculate(a:number,b?:number):number{// 可选参数可能为undefined,需先判断再使用returnb?a+b:a;}// 正确调用方式calculate(10);// 只传必选参数,b为undefinedcalculate(10,20);// 传必选+可选参数// 错误示例:可选参数放必选参数前面// function wrongCalculate(b?: number, a: number): number { ... } // 报错避坑点:可选参数的类型会自动推导为“对应类型 | undefined”,所以必须先判断是否存在,再使用,避免运行时错误。
2. 默认参数:给“可选任务”设“默认值”
如果可选参数有默认值,可以直接在参数列表里赋值,此时参数会自动被视为可选参数,无需再加?。同时,默认参数的类型会自动推导,也可以手动标注。
// 默认参数c:默认值为10,类型自动推导为numberfunctionaddWithDefault(a:number,b:number,c=10):number{returna+b+c;}// 正确调用方式addWithDefault(1,2);// c使用默认值10,结果13addWithDefault(1,2,20);// 覆盖默认值,结果23// 手动标注默认参数类型(适合默认值为复杂类型时)functiongreet(name:string,greeting:string="Hello"):string{return`${greeting},${name}`;}小技巧:默认参数可以放在必选参数中间,但调用时如果要跳过默认参数,需要传undefined(不推荐,易混淆,建议默认参数都放最后)。
三、剩余参数:让执行者“灵活接收批量任务”
有些场景下,函数的参数数量不固定(比如求和函数,可传任意个数字),这时候就需要剩余参数——用...标记,接收所有剩余的参数,并存为一个数组。
// 剩余参数args:接收所有后续参数,类型为number[]functionsum(...args:number[]):number{returnargs.reduce((total,curr)=>total+curr,0);}// 正确调用:传任意个number类型参数sum(1);// 结果1sum(1,2,3);// 结果6sum(10,20,30,40);// 结果100// 剩余参数结合必选参数functionjoinStr(separator:string,...strs:string[]):string{returnstrs.join(separator);}joinStr("-","a","b","c");// 结果"a-b-c"避坑点:剩余参数必须是函数的最后一个参数,且类型只能是数组类型(比如number[]、string[]),不能是单个类型。
四、函数重载:给执行者配“多套工作手册”
有些函数需要处理不同类型/数量的参数,返回不同类型的结果(比如“获取用户信息”,传ID返回单个用户,传数组ID返回用户列表)。这时候就需要函数重载——为同一个函数定义多个“类型签名”,让函数根据入参自动匹配对应的约束规则。
函数重载的核心是“先声明,后实现”:先定义多个类型签名,再写一个兼容所有签名的函数实现。
// 1. 定义函数重载签名(多套工作手册)functiongetUser(id:number):{id:number;name:string};// 传number,返回单个用户functiongetUser(ids:number[]):{id:number;name:string}[];// 传number[],返回用户列表// 2. 实现函数(兼容所有签名,需处理所有场景)functiongetUser(idOrIds:number|number[]):{id:number;name:string}|{id:number;name:string}[]{if(typeofidOrIds==="number"){// 处理单个ID场景return{id:idOrIds,name:`用户${idOrIds}`};}else{// 处理ID数组场景returnidOrIds.map(id=>({id,name:`用户${id}`}));}}// 正确调用:自动匹配对应签名constuser=getUser(1001);// 匹配第一个签名,返回单个用户constuserList=getUser([1001,1002]);// 匹配第二个签名,返回用户列表// 错误示例:无对应签名// getUser("1001"); // 报错:无接收string类型的签名避坑点:函数实现的参数类型和返回值类型,必须兼容所有重载签名,不能遗漏任何一种场景。同时,重载签名要精准区分,避免模糊冲突(比如两个签名参数数量相同、类型兼容,TS无法区分)。
五、类型别名:给函数类型“起别名”,提升复用性
如果多个函数的类型签名相同(比如多个回调函数都是“接收number,返回string”),反复写完整类型会很冗余。这时候可以用type给函数类型起别名,实现类型复用。
// 定义函数类型别名:接收number,返回stringtypeNumberToStringFn=(num:number)=>string;// 复用别名定义函数constformatNum:NumberToStringFn=(num)=>`数字:${num}`;constconvertNum:NumberToStringFn=(num)=>num.toString();// 别名也可用于函数参数(比如回调函数参数)functionprocessNumber(num:number,callback:NumberToStringFn):string{returncallback(num);}// 调用:回调函数自动匹配别名类型processNumber(10,(num)=>`处理后:${num}`);// 结果"处理后:10"小技巧:函数类型别名可以结合复合类型、泛型(后续章节)使用,比如定义“接收用户对象,返回布尔值”的别名,适配复杂业务场景。
六、实践:回调函数的类型约束(高频场景)
回调函数是前端开发的高频场景(异步请求、事件监听、数组方法等),用TS约束回调类型,能避免“回调参数乱传、返回值不符合预期”的问题。咱们结合两个核心场景实战。
场景1:异步回调(如定时器、请求回调)
// 定义异步函数,接收回调参数typeAsyncCallback=(result:string)=>void;// 回调:接收string,无返回值functionfetchData(callback:AsyncCallback){// 模拟异步请求setTimeout(()=>{callback("请求成功,返回数据");// 回调参数必须是string,否则报错},1000);}// 调用:回调函数参数自动匹配类型fetchData((res)=>{console.log(res);// res被推导为string,有代码提示});场景2:事件回调(如DOM事件)
DOM事件回调有自带的类型(如MouseEvent、KeyboardEvent),无需手动定义,直接复用TS内置类型即可。
// 获取按钮元素(用非空断言!,排除null)constbtn=document.getElementById("btn")!;// 点击事件回调:参数e类型为MouseEventbtn.addEventListener("click",(e:MouseEvent)=>{console.log("点击位置:",e.clientX,e.clientY);// e有完整的事件属性提示});// 键盘事件回调:参数e类型为KeyboardEventdocument.addEventListener("keydown",(e:KeyboardEvent)=>{console.log("按下按键:",e.key);});避坑点:DOM元素获取可能返回null,用!非空断言(确认元素存在),或先判断元素是否存在,再绑定事件,避免类型错误。
七、本章小结:函数类型的核心是“精准约束输入输出”
这一章咱们吃透了函数类型的全场景约束,核心逻辑可以总结为:函数类型标注的本质是“锁定输入输出的规则”——可选参数、默认参数、剩余参数负责“灵活适配输入场景”,函数重载负责“多场景差异化约束”,类型别名负责“提升类型复用性”。
新手容易踩的坑:一是可选参数放错位置;二是忽略可选参数的undefined判断;三是函数重载实现不兼容所有签名;四是回调函数滥用any。这些都需要通过多写实战代码来磨合,养成“先定规则,再写逻辑”的习惯。
下一章咱们要解锁TS的“结构化约束神器”——接口(Interface),学会用接口统一复杂对象、函数的类型规则,让代码更规范、更易维护。咱们下一章见!