TypeScript高频面试题
基础概念
-
问:什么是TypeScript?它与JavaScript的关系是什么?
答:TypeScript是JavaScript的超集,添加了类型系统和一些其他特性。它由Microsoft开发,可以编译成纯JavaScript。其主要目的是增强JavaScript的可维护性、可靠性,特别适用于大型项目。
-
问:TypeScript的主要优势是什么?
答:
- 静态类型检查,减少运行时错误
- 增强IDE的代码提示和自动补全
- 更好的代码组织和可维护性
- 支持最新的ECMAScript特性
- 适用于大型复杂项目
-
问:
interface和type有什么区别?答:
- 接口可以被继承和实现,类型别名不能
- 接口可以被合并声明,类型别名不能
- 接口只能描述对象/类/函数的结构,而类型别名可以表示任何类型
- 类型别名可以使用联合类型、交叉类型等高级类型操作
类型系统
-
问:TypeScript中的基本数据类型有哪些?
答:
- 布尔值(boolean)
- 数字(number)
- 字符串(string)
- 数组(Array
<T>或T[]) - 元组(tuple)
- 枚举(enum)
- any(任意类型)
- void(无返回值)
- null和undefined
- never(永不存在的值的类型)
- object(非原始类型)
-
问:什么是泛型?如何使用?
答:泛型允许在定义函数、接口或类时,不预先指定具体的类型,而是在使用时再指定类型。通过
<T>语法来定义泛型参数。typescript 体验AI代码助手 代码解读 复制代码 function identity<T>(arg: T): T { return arg; } let output = identity<string>("myString"); -
问:解释
unknown和any的区别答:
any类型会跳过类型检查,允许任何操作unknown是类型安全的any,必须通过类型断言或类型收窄后才能进行具体操作- 使用
unknown比any更安全,因为它要求在使用前进行类型检查
高级特性
-
问:什么是联合类型和交叉类型?
答:
- 联合类型(Union)表示值可以是几种类型之一,使用
|符号:type MyType = string | number; - 交叉类型(Intersection)表示将多个类型合并为一个类型,使用
&符号:type Combined = Type1 & Type2;
- 联合类型(Union)表示值可以是几种类型之一,使用
-
问:什么是字面量类型?
答:字面量类型是一种更具体的类型,表示特定的值而不是广泛的类型:
typescript 体验AI代码助手 代码解读 复制代码 type Direction = 'North' | 'East' | 'South' | 'West'; let dir: Direction = 'North'; // 只能是这四个值之一 -
问:什么是索引签名?
答:索引签名用于描述对象的索引类型,允许额外的属性:
typescript 体验AI代码助手 代码解读 复制代码 interface StringArray { [index: number]: string; }
高级类型操作
-
问:如何在TypeScript中实现函数重载?
答:通过定义多个函数签名,然后实现一个通用版本:
typescript 体验AI代码助手 代码解读 复制代码 function add(a: string, b: string): string; function add(a: number, b: number): number; function add(a: any, b: any): any { return a + b; } -
问:什么是类型守卫?如何使用?
答:类型守卫是一种在运行时检查变量类型的表达式,帮助TypeScript在特定代码块中缩小变量类型:
typescript 体验AI代码助手 代码解读 复制代码 // 使用 typeof function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } return padding + value; } // 使用 instanceof if (pet instanceof Fish) { pet.swim(); } // 使用自定义类型谓词 function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; } -
问:什么是条件类型?
答:条件类型根据条件选择不同的类型,类似于三元运算符:
typescript 体验AI代码助手 代码解读 复制代码 type Check<T> = T extends string ? string : number;
实践问题
-
问:如何在TypeScript中模拟枚举的字符串值?
答:可以使用字面量联合类型或as const:
typescript 体验AI代码助手 代码解读 复制代码 // 方法1:字面量联合类型 type Direction = 'North' | 'East' | 'South' | 'West'; // 方法2:使用as const const Directions = { North: 'North', East: 'East', South: 'South', West: 'West' } as const; type Direction = typeof Directions[keyof typeof Directions]; -
问:什么是映射类型?Partial和Required有什么作用?
答:映射类型基于旧类型创建新类型,改变属性:
Partial<T>:将所有属性变为可选Required<T>:将所有属性变为必需Readonly<T>:将所有属性变为只读Pick<T, K>:从T中选择一组属性KOmit<T, K>:从T中排除一组属性K
-
问:如何处理TypeScript中的异步操作类型?
答:使用Promise和async/await类型标注:
typescript 体验AI代码助手 代码解读 复制代码 async function fetchData(): Promise<UserData> { const response = await fetch('/api/user'); const data: UserData = await response.json(); return data; }
配置与工具
-
问:tsconfig.json的主要配置项有哪些?
答:主要配置项包括:
-
compilerOptions:编译选项 -
include/exclude:指定要编译的文件 -
extends:继承其他配置文件 -
重要的编译选项:
target:编译目标ES版本module:模块系统strict:严格模式esModuleInterop:兼容性设置outDir:输出目录
-
-
问:如何在TypeScript中使用第三方库?
答:
- 优先使用有TypeScript类型定义的库
- 对于没有类型定义的库,可以使用DefinitelyTyped提供的类型定义文件:
npm install @types/库名 - 也可以自己创建声明文件:
*.d.ts
-
问:如何在TypeScript中处理模块声明?
答:使用声明文件(.d.ts)来为模块提供类型定义:
typescript 体验AI代码助手 代码解读 复制代码 // 为一个JS模块提供声明 declare module 'my-module' { export function myFunction(): void; export const myVariable: string; }进阶概念
-
问:解释一下TypeScript中的协变与逆变
答:协变与逆变是关于类型兼容性的概念:
- 协变(covariance):如果A是B的子类型,那么Array也是Array的子类型
- 逆变(contravariance):如果A是B的子类型,那么Function是Function的子类型
- 在TypeScript中,数组是协变的,而函数参数是逆变的
- 问:什么是可辨识联合类型(Discriminated Unions)?
答:可辨识联合是TypeScript中一种特殊的联合类型,具有共同的单例类型属性—"标签"。这使得类型收窄更加容易:
typescript
体验AI代码助手
代码解读
复制代码
interface Square {
kind: "square"; // 标签
size: number;
}
interface Rectangle {
kind: "rectangle"; // 标签
width: number;
height: number;
}
type Shape = Square | Rectangle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.width * s.height;
}
}
- 问:什么是infer关键字?如何使用?
答:infer关键字用在条件类型中推断类型变量:
typescript
体验AI代码助手
代码解读
复制代码
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// 使用例子:
function foo() { return 123; }
type FooReturn = ReturnType<typeof foo>; // number
工程实践
- 问:如何处理TypeScript中的模块路径别名?
答:在tsconfig.json中配置paths选项:
json
体验AI代码助手
代码解读
复制代码
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"]
}
}
}
要注意的是,这只处理了TypeScript编译时的路径解析,配合使用Webpack等构建工具的alias配置才能完全支持。
- 问:TypeScript中的声明合并是什么?
答:声明合并是指编译器将多个同名声明合并为一个定义。常见于接口合并:
typescript
体验AI代码助手
代码解读
复制代码
interface Box {
height: number;
}
interface Box {
width: number;
}
// 两个接口会合并为:
// interface Box {
// height: number;
// width: number;
// }
- 问:keyof、typeof和索引访问类型如何使用?
答:这些是TypeScript的操作符:
typescript
体验AI代码助手
代码解读
复制代码
interface Person {
name: string;
age: number;
}
// keyof获取对象的键
type PersonKeys = keyof Person; // "name" | "age"
// typeof获取变量的类型
const p = { name: "Tom", age: 25 };
type P = typeof p; // { name: string; age: number }
// 索引访问类型
type PersonName = Person["name"]; // string
高级类型操作
- 问:如何实现ReturnType、Parameters等工具类型?
答:使用条件类型和infer关键字:
typescript
体验AI代码助手
代码解读
复制代码
// 获取函数返回类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 获取函数参数类型
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
function greet(name: string, age: number): string {
return `Hello, ${name}! You are ${age} years old.`;
}
type GreetReturn = MyReturnType<typeof greet>; // string
type GreetParams = MyParameters<typeof greet>; // [string, number]
- 问:什么是TypeScript中的命名空间(namespace)?它与模块有什么区别?
答:命名空间是一种将代码组织在一个命名容器内的方式:
- 命名空间(内部模块):使用
namespace关键字定义,主要用于组织代码 - 模块(外部模块):使用ES模块系统的
import/export语法,更适合现代应用
现代TypeScript开发推荐使用ES模块系统而非命名空间。
- 问:如何在TypeScript中使用装饰器(Decorators)?
答:装饰器是实验性特性,需在tsconfig.json中启用:
json
体验AI代码助手
代码解读
复制代码
{
"compilerOptions": {
"experimentalDecorators": true
}
}
基本用法:
typescript
体验AI代码助手
代码解读
复制代码
function logger(target: any) {
console.log(`Class name: ${target.name}`);
}
@logger
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
- 问:TypeScript中的Mixins是什么?如何实现?
答:Mixins是一种代码复用模式,允许将多个类的功能合并:
typescript
体验AI代码助手
代码解读
复制代码
// 定义Mixin工厂
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = Date.now();
};
}
function Activatable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
isActive = false;
activate() { this.isActive = true; }
deactivate() { this.isActive = false; }
};
}
// 使用Mixins
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
const TimestampedUser = Timestamped(User);
const TimestampedActivatableUser = Activatable(TimestampedUser);
const user = new TimestampedActivatableUser("John");
console.log(user.timestamp);
user.activate();
- 问:解释一下TypeScript中的
never类型的使用场景
答:never类型表示永不存在的值的类型,主要用于:
- 函数抛出异常或无法正常返回值的返回类型
- 作为联合类型中不可能出现的类型
- 用于完整性检查,比如确保switch语句涵盖所有可能情况
- 问:如何在TypeScript中处理this类型?
答:可以使用this参数声明:
typescript
体验AI代码助手
代码解读
复制代码
interface Counter {
count: number;
increase(): this;
reset(): void;
}
class SimpleCounter implements Counter {
count = 0;
increase() {
this.count++;
return this;
}
reset() {
this.count = 0;
}
}
const counter = new SimpleCounter().increase().increase(); // 链式调用