TypeScript 入门:从函数重载到泛型与高级类型
一、函数进阶:重载、箭头函数与类型注解
(1)函数重载:定义多套调用规则
核心作用:给同一个函数定义多套参数和返回值的类型,让不同调用方式都能获得正确的类型提示。
// 重载声明:只写类型,不写实现
function add(a: number, b: number): number;
function add(a: string, b: string): string;
// 函数实现:统一处理所有情况
function add(a: number | string, b: number | string): number | string {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
if (typeof a === 'string' && typeof b === 'string') {
return a + b;
}
throw new Error('参数类型不匹配');
}
新手注意:重载声明必须写在实现之前,而且实现的参数和返回值类型必须兼容所有重载的情况。
(2)箭头函数 vs 普通函数:彻底分清差异
箭头函数和普通函数的区别,是面试高频考点,也是新手最容易踩坑的地方,我整理了核心差异:
1.this 指向:
普通函数:由调用方式决定(谁调用指向谁)
箭头函数:继承外层作用域的 this,没有自己的 this
2.arguments 对象:
普通函数:自带 arguments,接收所有参数
箭头函数:没有 arguments,需用剩余参数 ...args 替代
3.构造函数:
普通函数:可以用 new 调用,生成实例
箭头函数:不能作为构造函数,new 调用会报错
4.prototype 属性
普通函数:有 prototype
箭头函数:没有 prototype
场景示例(定时器回调的 this 坑):
class Timer {
count = 0;
start() {
// 普通函数:this 指向 window/undefined,无法访问 count
setTimeout(function() {
console.log(this.count); // undefined
}, 1000);
// 箭头函数:继承外层 start 方法的 this,指向实例
setTimeout(() => {
console.log(this.count); // 0(正确访问)
}, 1000);
}
}
(3)函数类型注解:给回调函数定义类型
当函数作为参数传递(回调函数)时,必须给它定义类型,否则 TS 会报错。
1.直接内联定义:
function fetchData(callback: (data: string, code: number) => void) {
callback('success', 200);
}
2.用 type 定义类型别名(简化写法):
type Callback = (data: string, code: number) => void;
function fetchData(callback: Callback) {
callback('success', 200);
}
二、枚举与流程控制:规范常量与循环安全
(1)枚举(enum):给常量起个 “名字”
枚举是 TS 特有的语法,用来定义一组命名常量,避免 “魔法值”。
// 数值枚举(默认从 0 开始递增)
enum Status {
Pending, // 0
Success, // 1
Fail // 2
}
// 字符串枚举(必须手动赋值,无自动递增)
enum OrderStatus {
Pending = 'pending',
Success = 'success',
Fail = 'fail'
}
关键特性:
1.数值枚举支持双向映射:既可以用 Status[0] 拿到 'Pending',也可以用 Status.Pending 拿到 0。
2.字符串枚举只有单向映射:只能用 OrderStatus.Pending 拿到 'pending',不能反向映射。
3.const enum 常量枚举:编译时会被直接替换成值,不会生成额外代码,适合只需要值、不需要反向映射的场景。
(2)循环语句:for...in 的类型安全写法
TS 中 for...in 遍历对象时,默认会把 key 推断为 string,直接访问对象属性会报错,需要手动处理类型安全:
const user = { name: '张三', age: 18 };
// 正确写法:用 keyof 约束类型 + hasOwnProperty 过滤原型链属性
for (const key in user) {
// 1. 用 keyof 把 key 断言为对象的键类型
const k = key as keyof typeof user;
// 2. 过滤原型链上的属性,避免遍历到继承的属性
if (user.hasOwnProperty(k)) {
console.log(user[k]);
}
}
(3)空值安全运算符:?. 和??怎么用?
这两个运算符是 TS 开发中避免 Cannot read property 'xxx' of undefined 错误的神器:
1.可选链 ?.:
安全访问嵌套属性,遇到 null/undefined 会直接返回 undefined,不会报错。
const user = { info: { name: '张三' } };
// 不用 ?.:如果 info 是 undefined,会直接报错
console.log(user.info?.name); // 张三
console.log(user.address?.city); // undefined(不会报错)
2.空值合并 ??:
仅当左侧值为 null/undefined 时,才返回右侧默认值,和 || 有本质区别。
const count = 0;
console.log(count || 10); // 10(0 被视为假值,会走默认值)
console.log(count ?? 10); // 0(只有 null/undefined 才会走默认值)
三、泛型:告别重复类型定义,实现通用代码
泛型是 TS 中最核心的特性之一,一开始我也觉得很难懂,现在终于理清了基础用法。
(1)泛型的核心作用:解决类型复用问题
如果没有泛型,我们需要为每种类型写重复的函数:
// 不使用泛型:重复代码
function returnString(arg: string): string {
return arg;
}
function returnNumber(arg: number): number {
return arg;
}
用泛型可以写一个通用函数,适配所有类型:
// 使用泛型:一个函数搞定所有类型
function identity<T>(arg: T): T {
return arg;
}
// 自动推断类型
const str = identity('hello'); // 类型推断为 string
const num = identity(123); // 类型推断为 number
// 手动指定类型
const bool = identity<boolean>(true);
(2)多泛型参数:适配多种类型
可以定义多个泛型参数,处理不同类型的参数:
function pair<S, U>(str: S, num: U): [S, U] {
return [str, num];
}
const result = pair('age', 18); // 类型为 [string, number]
(3)泛型接口与泛型类:定义通用数据结构
泛型接口:
定义可复用的结构
interface Box<T> {
value: T;
getValue(): T;
}
const stringBox: Box<string> = {
value: 'hello',
getValue() {
return this.value;
}
};
泛型类:
实现通用数据结构(比如栈)
class Stack<T> {
private data: T[] = [];
push(item: T) {
this.data.push(item);
}
pop(): T | undefined {
return this.data.pop();
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
const top = numberStack.pop(); // 类型为 number | undefined
四、高级类型拓展:字面量、类型断言与交叉类型
这部分知识点虽然不复杂,但能让 TS 的类型约束更精准。
(1)字面量类型 + 联合类型:限制固定取值
字面量类型就是把变量的类型限制为某个固定值,搭配联合类型可以实现枚举的效果,还更灵活:
// 只能取这三个值,其他值会报错
type Status = 'pending' | 'success' | 'fail';
let orderStatus: Status = 'pending';
orderStatus = 'success'; // 合法
orderStatus = 'cancel'; // 报错:类型不匹配
(2)类型断言 as:手动告诉 TS 变量的类型
当 TS 无法自动推断出正确类型时,可以用 as 手动指定类型,注意:断言只在编译阶段生效,不会改变变量的实际类型。
// 比如从 DOM 获取元素,TS 只知道它是 HTMLElement,我们手动断言为 HTMLInputElement
const input = document.getElementById('username') as HTMLInputElement;
input.value = '张三'; // 现在可以安全访问 value 属性了
(3)交叉类型 &:合并多个类型
交叉类型用 & 表示,用来合并多个类型,要求变量同时满足所有类型的属性。
interface User {
name: string;
age: number;
}
interface Address {
city: string;
zip: string;
}
// 合并两个接口,必须同时包含所有属性
type UserInfo = User & Address;
const user: UserInfo = {
name: '张三',
age: 18,
city: '北京',
zip: '100000'
};
注意:如果是基础类型交叉(比如 string & number),结果会是 never 类型,因为没有任何值同时属于两种类型。
五、易错点复盘
1.函数重载的实现必须兼容所有重载声明,否则会报类型错误。
2.箭头函数没有自己的 this,不要用它定义对象方法(会继承外层 this,容易出错)。
3.for...in 遍历对象时,一定要用 keyof 做类型断言,否则访问属性会报错。
4.不要滥用 any,它会让 TS 失去类型安全检查,优先用 unknown 替代。
5.可选链 ?. 和空值合并 ?? 要分清:?. 访问属性,?? 兜底空值。
6.交叉类型合并对象时,属性名重复且类型冲突会报错(比如 {a: string} & {a: number} 会变成 never)。
更多推荐

所有评论(0)