一、函数进阶:重载、箭头函数与类型注解

(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)。

更多推荐