TypeScript面试题
本文最后更新于 2025年10月31日 中午
更多:https://juejin.cn/post/7321542773076082699
TS 和 JS
优点:
- 静态类型检查。类型错误在 JS 里只能在运行时发现,但是 TS 在编译阶段就能发现,比如访问了不存在的属性,调用方法参数类型不对,这些在写代码时就会报错
- 更强的 IDE 支持。比如自动补全、跳转到定义和类型推导,开发效率更高
- 在团队协作中很有价值。因为接口契约清晰,维护成本更低
- TS 是 JS 的超集,兼容性很好,能用到 ES6+ 的新特性,还提供泛型、枚举、接口等高级功能,让代码更健壮和可扩展
缺点:
- 学习成本高一点
- 类型定义初期可能比较繁琐,但长远来看收益更大
any 和 unknown
| 特性 | any |
unknown |
|---|---|---|
| 类型检查 | 不做检查 | 必须先检查才能使用 |
| 安全性 | 低(可能埋雷) | 高(强制开发者做类型守卫) |
| 赋值 | 可以赋值给任何类型 | 只能赋值给 any 或 unknown |
| 使用场景 | 快速原型、临时绕过类型 | 接口数据不确定、需要后续收窄类型 |
两者都能接收任意类型的值,但 any 会关闭类型检查,可以随便用,非常不安全;unknown 更安全,它要求我们在使用之前必须做类型缩小。通常情况下,如果不确定类型推荐用 unknown 而不是 any。
interface 和 type
| 特性 | interface | type |
|---|---|---|
| 定义对象结构 | ✅ 支持 | ✅ 支持 |
| 扩展方式 | ✅ 支持(extends) |
✅ 支持(&) |
| 声明合并 | ✅ 支持(同名自动合并) | ❌ 不支持(再定义同名会报错) |
| 联合/交叉类型 | ❌ 不支持 | ✅ 支持 |
| 元组/基础类型 | ❌ 不支持 | ✅ 支持 |
| class 实现 | ✅ 支持 implements |
❌ 不支持 |
| 使用场景 | 面向对象风格,约束对象/类 | 灵活定义各种复杂类型(联合、条件、元组) |
在 TypeScript 中,interface 和 type 都能描述对象结构,但有一些区别:
interface偏于面向对象和类,可用extends扩展,可被class implements,可声明合并type更灵活,能定义对象结构,以及联合类型、交叉类型、元组和条件类型,但不能声明合并
总体来说,如果是对象和类的契约接口,推荐用 interface;而如果需要更复杂的类型表达式,比如联合类型或工具类型,推荐用 type。
never 和 void
| 特性 | void |
never |
|---|---|---|
| 含义 | 没有返回值(可能 undefined/null) |
不可能有值(函数无法结束/抛错) |
| 使用场景 | 常用于函数没有返回值 | 抛异常、死循环、不可能发生的分支 |
| 是否能赋值 | void 变量可赋值为 undefined/null |
never 变量无法赋值任何值 |
| 编译器语义 | “我不关心返回值” | “这里绝对不应该到达” |
元组和数组
| 特性 | 数组(Array) | 元组(Tuple) |
|---|---|---|
| 长度 | 不固定 | 固定(或有限制) |
| 元素类型 | 必须统一(或联合类型) | 每个位置类型可不同 |
| 适用场景 | 存放一类数据集合 | 表示一组有顺序的、结构化的数据 |
| 示例 | [1, 2, 3] |
[string, number] → ["Tom", 25] |
1 | |
Omit 和 Pick
Pick<T, K>是从类型 T 里挑选出一部分属性,生成新类型Omit<T, K>是从类型 T 里排除掉一部分属性,生成新类型
它们的作用正好相反,一个是“取子集”,一个是“去子集”。
1 | |
联合类型和交叉类型
| 特性 | 联合类型 A | B |
交叉类型 A & B |
|---|---|---|
| 关系 | 或者(并集) | 并且(交集) |
| 含义 | 值可能是几种类型之一 | 值必须同时符合所有类型 |
| 示例 | string | number → 字符串或数字 |
{name} & {age} → 必须有 name 和 age |
| 使用场景 | 参数多种类型,灵活 | 组合多个对象类型,复用性强 |
类型断言和类型推断
类型断言可以用于手动指定一个值的具体类型,即允许变量从一种类型更改为另一种类型。
类型推断是 TS 根据赋值语句右侧的值自动推断变量的类型。
declare 关键字作用
declare 关键字在 TS 中用来声明已经存在的变量、函数、类、模块等,不需要在 TS 文件中实现。它只在编译时生效,不会生成运行时代码。常见场景:
- 声明全局变量/函数/类
- 声明第三方库的类型
- 写
.d.ts类型声明文件
TS 支持的访问修饰符
TypeScript 在类中支持 public、private、protected 三种访问修饰符:
public表示对外公开,默认值private表示成员只能在类的内部访问protected表示成员只能在类和子类中访问
还有 readonly(只读属性)和 static(静态成员)修饰符,用来增强类的封装性和可维护性。
TS 中的泛型是什么
泛型是给类型定义参数,让函数、接口、类在编写时不预设具体类型,而在使用时再确定类型:
- 普通函数参数:数据的占位符
- 泛型:类型的占位符
1 | |
泛型的核心作用:
- 可重用:一个函数/接口/类,可以适配多种类型
- 类型安全:编译器会推导类型,减少 any 带来的风险
- 灵活性:可以在使用时指定或让 TS 自动推导
1 | |
TS 中的类型有哪些
类型系统表示语言支持的不同类型的值。它在程序存储或者操作所提供的值之前检查其有效性。可以分为两种类型:
- 内置:包括数字(number),字符串(string),布尔值(boolean),无效(void),空值(null),未定义(undefined),bigint,符号(symbol)
- 用户定义:包括枚举(enums),类(classes),接口(interfaces),数组(arrays),元组(tuple)
TS 中什么是装饰器
是一种特殊类型的声明,它能过被附加到类声明,方法,属性或者参数上,可以修改类的行为。
通俗的来说就是一个方法,可以注入到类,方法,属性参数上来扩展类,属性,方法,参数的功能。装饰器的分类: 类装饰器、属性装饰器、方法装饰器、参数装饰器等。
解释 TS 的 mixin
在 TS 里,Mixin 是一种通过组合来复用类逻辑的方式。比如我有 Jumpable 和 Runnable 这样的类,我可以把它们混入到 SuperPerson 里,让它同时拥有 jump 和 run 方法。
在 TS 中实现 Mixin 通常有两种方式:一种是用 implements 结合 applyMixins 函数复制原型方法,另一种是更常用的函数式 Mixin,返回一个扩展了基础类的新类。
相比传统继承,Mixin 更灵活,适合为类扩展独立的功能,如在前端项目里给某些对象批量加日志、权限或动画能力。
什么是 TS 映射文件
TS 映射文件就是 Source Map 文件,通常扩展名是 .js.map。当把 TS 编译成 JS 时,TS 编译器可以生成一个映射文件,它的作用是:建立 编译后的 JS 代码 和 原始 TS 代码 的对应关系。方便调试时直接回溯到 TS 源码,而不是看编译后的 JS。
tsconfig.json
在 tsconfig.json 文件中,可以指定不同的选项来告诉编译器如何编译当前项目。目录中包含 tsconfig.json 文件,表明该目录是 TypeScript 项目的根目录。
1 | |
命名空间与模块区别
命名空间主要用于全局作用域下的组织代码,防止命名冲突,而模块是基于文件的作用域隔离机制,通过 import/export 实现代码复用。随着 ES6 模块的普及,更推荐使用模块而不是命名空间。
| 对比点 | 命名空间(Namespace) | 模块(Module) |
|---|---|---|
| 定义方式 | namespace 关键字 |
一个文件就是一个模块 |
| 导入导出 | export 导出,在同一文件或全局作用域使用 |
使用 export / import |
| 使用场景 | 早期全局作用域代码组织,解决命名冲突 | 现代前端/Node.js 工程化开发 |
| 作用域 | 命名空间是全局的,容易被打包进一个文件 | 模块有自己作用域,天然隔离 |
| 推荐程度 | 不推荐(过时,适合小项目/老项目) | 强烈推荐(符合 ES6 标准) |