阮一峰《TypeScript 教程》学习笔记——类型系统
1. 一段话总结
TypeScript 函数类型需明确参数类型与返回值类型(返回值类型可省略,TS 自动推断),变量赋值函数时有两种类型写法(推断式、显式箭头式),且支持用 type/interface 定义类型别名;存在Function 类型(无约束,不推荐)与箭头函数类型(写法与普通函数类似,需注意参数圆括号);参数支持可选参数(尾部可加 ?)、默认值(与可选不可共存)、解构(需显式声明解构类型)、rest 参数(数组/元组类型,可嵌套)、只读参数(readonly 修饰,禁止内部修改);返回值类型有void(正常无返回,允许 undefined/null) 与 never(函数不正常结束,如抛错/死循环);函数内部可声明局部类型(仅内部有效),支持高阶函数(返回值为函数) 与 函数重载(多类型声明+单一实现,需按精度排序),还可通过 new 关键字声明构造函数类型(用于类或构造函数参数)。
2. 思维导图

3. 详细总结
一、函数类型基础声明
TypeScript 函数需明确参数类型与返回值类型,核心规则与写法如下:
1. 基本写法
- 函数声明时,参数类型写在参数名后,返回值类型写在参数列表圆括号后(返回值可省略,TS 自动推断):
function hello(txt:string):void { console.log('hello ' + txt); } // 显式返回值类型 function hello(txt:string) { console.log('hello ' + txt); } // 省略返回值类型(TS 推断为 void) - 变量赋值函数的两种类型写法:
| 写法类型 | 语法格式 | 示例 | 注意事项 |
|---|---|---|---|
| 推断式 | const 变量 = function(参数:类型) { ... } | const hello = function(txt:string) { console.log(txt); } | 依赖右侧函数自动推断变量类型 |
| 显式箭头式 | const 变量: (参数:类型)=>返回值类型 = function(参数) { ... } | const hello: (txt:string)=>void = function(txt) { console.log(txt); } | 1. 参数必须加圆括号;2. 参数名必写(否则视为“参数名=string,类型=any”);3. 变量类型与函数参数名可不同 |
2. 类型别名与接口声明
- 用
type定义函数类型别名(推荐,简化重复类型):type MyFunc = (txt:string)=>void; const hello:MyFunc = function(txt) { console.log(txt); }; - 用
interface定义函数类型(对象写法翻版,适用于函数带属性的场景):interface MyFunc { (a:number, b:number):number; } const add:MyFunc = (a,b)=>a+b;
3. Function 类型
- 表示“任意函数”,参数与返回值均为
any(无约束,不推荐):function doSomething(f:Function) { return f(1,2,3); } // f 可接受任意参数,返回任意值
二、箭头函数类型
- 类型写法与普通函数类似:参数类型写在参数后,返回值类型写在参数圆括号后:
const repeat = (str:string, times:number):string => str.repeat(times); - 与“函数类型的箭头表示”区分:前者是“箭头函数的类型声明”,后者是“函数类型的简写”(如参数类型):
// 函数类型的箭头表示(参数 fn 的类型) function greet(fn:(a:string)=>void):void { fn('world'); }
三、参数类型细节
1. 可选参数
- 语法:参数名后加
?,表示“参数可省略,类型为‘原始类型|undefined’”:function f(x?:number) { return x; } f(); // 正确;f(undefined); // 正确 - 限制:可选参数只能在参数列表尾部(不能在可选参数后加必填参数),否则报错:
let myFunc: (a?:number, b:number)=>number; // 报错(可选参数在必填前)
2. 参数默认值
- 与 JavaScript 语法一致,默认值参数自动为“可选”,不能与
?同时使用(否则报错):function createPoint(x:number=0, y:number=0):[number,number] { return [x,y]; } createPoint(); // [0,0](正确) function f(x?:number=0) { ... } // 报错(可选与默认共存) - 非尾部默认值:调用时不能省略,需显式传入
undefined触发默认值:function add(x:number=0, y:number) { return x+y; } add(undefined, 1); // 1(正确);add(1); // 报错(y 未传)
3. 参数解构
- 需显式声明解构对象/数组的类型,可结合
type简化:// 解构数组 function f([x,y]:[number,number]) { ... } // 解构对象(原始写法) function sum({a,b,c}:{a:number; b:number; c:number}) { console.log(a+b+c); } // 解构对象(结合 type 简化) type ABC = {a:number; b:number; c:number}; function sum({a,b,c}:ABC) { console.log(a+b+c); }
4. rest 参数
- 表示“剩余所有参数”,支持数组(成员类型相同)或元组(成员类型不同),可嵌套/结合解构:
// 数组类型 rest 参数 function joinNumbers(...nums:number[]) { ... } // 元组类型 rest 参数(支持可选成员) function f(...args:[boolean, string?]) { ... } // 嵌套 rest 参数 function f(...args:[boolean, ...string[]]) { ... } // 结合解构的 rest 参数 function repeat(...[str, times]:[string, number]):string { return str.repeat(times); }
5. 只读参数
- 用
readonly修饰参数类型(仅支持数组/对象类型),禁止函数内部修改参数:function arraySum(arr:readonly number[]) {arr[0] = 0; // 报错(只读参数禁止修改) }
四、返回值类型(void vs never)
两种核心返回值类型的区别如下:
| 类型 | 核心含义 | 适用场景 | 允许返回值 | 与 JS 对应逻辑 |
|---|---|---|---|---|
| void | 函数正常执行结束,无返回值 | 仅执行副作用(如 console.log) | 1. 无 return;2. return undefined;3. return null(strictNullChecks 关闭时) | 函数默认返回 undefined |
| never | 函数不正常执行结束(无返回过程) | 1. 抛错函数;2. 死循环函数 | 无(函数无法执行到 return) | 函数执行中断(抛错)或无限执行 |
- 示例:
// void 类型(正常无返回) function f():void { console.log('hello'); } // never 类型(抛错函数) function fail(msg:string):never { throw new Error(msg); } // never 类型(死循环函数) const sing:()=>never = () => { while(true) { console.log('sing'); } };
五、其他重要特性
1. 局部类型
- 函数内部声明的类型(仅在函数内部有效,外部使用报错):
function hello(txt:string) {type Message = string; // 局部类型let newTxt:Message = 'hello ' + txt;return newTxt; } const newTxt:Message = hello('world'); // 报错(Message 外部不可见)
2. 高阶函数
- 定义:返回值为函数的函数(即“函数返回函数”):
const multiply = (x:number) => (y:number) => x*y; // 高阶函数 const double = multiply(2); double(3); // 6
3. 函数重载
- 定义:函数可接受“不同类型/个数的参数”,执行不同逻辑(多类型声明+单一实现)。
- 核心规则:
- 先写“精度高”的类型声明(如具体类型),再写“精度低”的(如联合类型),按顺序检查;
- 最后一个“实现函数”的类型需兼容所有声明类型;
- 实现内部需通过判断(如
typeof/Array.isArray)处理不同参数场景。
- 示例:
// 重载声明(精度从高到低) function reverse(str:string):string; function reverse(arr:any[]):any[]; // 实现函数(兼容所有声明) function reverse(stringOrArray:string|any[]):string|any[] {if (typeof stringOrArray === 'string') {return stringOrArray.split('').reverse().join('');} else {return stringOrArray.slice().reverse();} } - 替代方案:简单场景优先用联合类型替代重载(简化代码):
// 联合类型替代重载 function len(x:string|any[]):number { return x.length; }
4. 构造函数类型
- 定义:用
new关键字声明“需通过new调用的构造函数”类型,适用于类或构造函数参数:// 类定义 class Animal { numLegs:number = 4; } // 构造函数类型 type AnimalConstructor = new () => Animal; // 接受构造函数的函数 function create(c:AnimalConstructor):Animal { return new c(); } // 调用(类本质是构造函数) const a = create(Animal); - 双用途类型(支持“普通函数+构造函数”):用对象写法声明:
type F = {new (s:string):object; // 构造函数类型(n?:number):number; // 普通函数类型 };
4. 关键问题
问题1:TypeScript 中变量赋值函数时,“推断式写法”与“显式箭头式写法”的核心差异是什么?在什么场景下推荐用显式写法?
答案:
核心差异在于“类型是否显式声明”:
- 推断式写法(如
const hello = function(txt:string) { ... }):依赖右侧函数的参数/返回值类型,自动推断变量hello的类型,无需手动写类型,简洁高效; - 显式箭头式写法(如
const hello: (txt:string)=>void = function(txt) { ... }):需手动用“(参数:类型)=>返回值类型”声明变量类型,右侧函数参数类型可省略(TS 自动匹配变量类型)。
推荐显式写法的场景:
- 多人协作项目(显式类型提升代码可读性,明确函数输入输出约束);
- 函数类型复杂或需复用(如后续需将相同类型赋值给其他变量,可结合
type定义别名); - 防止后续代码意外修改函数类型(如避免误加不兼容的参数/返回值)。
问题2:TypeScript 中 void 类型与 never 类型都“无返回值”,如何精准区分两者的适用场景?结合示例说明。
答案:
两者的核心区分点是“函数是否正常执行结束”,适用场景完全不同:
-
void类型:适用于“函数正常执行结束,但无有意义返回值”的场景(仅执行副作用,如打印、修改外部变量),函数有完整的“执行-结束”流程,允许返回undefined或null(strictNullChecks开启时仅允许undefined);
示例:function logMsg(msg:string):void { console.log(msg); }(函数正常打印后结束,无返回值)。 -
never类型:适用于“函数无法正常执行结束”的场景(无“执行-结束”流程),函数要么抛错中断执行,要么陷入死循环无限执行,不可能有任何返回值;
示例1(抛错):function throwError(msg:string):never { throw new Error(msg); }(抛错后函数立即中断,无返回过程);
示例2(死循环):function infiniteLoop():never { while(true) { console.log('loop'); } }(函数永远执行,无法结束)。
简言之:void 是“有结束,无返回值”,never 是“无结束,无返回过程”。
问题3:函数重载中,“重载声明的排序”与“实现函数的类型兼容性”为何是关键?如果排序错误或实现类型不兼容,会导致什么问题?
答案:
函数重载的核心逻辑是“按声明顺序匹配参数类型,找到第一个匹配项后停止检查”,且“实现函数需覆盖所有声明场景”,因此排序与兼容性至关重要:
-
重载声明排序的关键原因:
TypeScript 按“声明顺序从先到后”检查参数类型,若“精度低”的声明(如联合类型、any)排在前面,会优先匹配所有调用,导致“精度高”的声明(如具体类型)永远无法被匹配,失去重载意义。
反例:function f(x:any):number; // 精度低的声明在前 function f(x:string):0|1; // 精度高的声明在后(永远无法匹配) function f(x:any):any { return x.length; } const a:0|1 = f('hi'); // 报错(f('hi') 匹配第一个声明,返回类型为 number,与 0|1 不兼容) -
实现函数类型兼容性的关键原因:
实现函数是重载的“唯一执行逻辑”,其参数/返回值类型必须能覆盖所有声明类型,否则会出现“声明支持的场景,实现无法处理”的矛盾,导致调用报错。
反例:function fn(x:boolean):void; // 声明1:参数为 boolean function fn(x:string):void; // 声明2:参数为 string function fn(x:number|string) { ... } // 实现参数类型为 number|string(不兼容声明1的 boolean) fn(true); // 报错(实现无法处理 boolean 类型参数,与声明1冲突)
若排序错误,会导致“重载声明失效”;若实现类型不兼容,会导致“部分声明场景无法调用”,两者均会破坏重载的设计目的。
