TypeScript高频面试题

44 阅读8分钟

TypeScript高频面试题

基础概念

  1. 问:什么是TypeScript?它与JavaScript的关系是什么?

    答:TypeScript是JavaScript的超集,添加了类型系统和一些其他特性。它由Microsoft开发,可以编译成纯JavaScript。其主要目的是增强JavaScript的可维护性、可靠性,特别适用于大型项目。

  2. 问:TypeScript的主要优势是什么?

    答:

    • 静态类型检查,减少运行时错误
    • 增强IDE的代码提示和自动补全
    • 更好的代码组织和可维护性
    • 支持最新的ECMAScript特性
    • 适用于大型复杂项目
  3. 问:interfacetype有什么区别?

    答:

    • 接口可以被继承和实现,类型别名不能
    • 接口可以被合并声明,类型别名不能
    • 接口只能描述对象/类/函数的结构,而类型别名可以表示任何类型
    • 类型别名可以使用联合类型、交叉类型等高级类型操作

类型系统

  1. 问:TypeScript中的基本数据类型有哪些?

    答:

    • 布尔值(boolean)
    • 数字(number)
    • 字符串(string)
    • 数组(Array<T>或T[])
    • 元组(tuple)
    • 枚举(enum)
    • any(任意类型)
    • void(无返回值)
    • null和undefined
    • never(永不存在的值的类型)
    • object(非原始类型)
  2. 问:什么是泛型?如何使用?

    答:泛型允许在定义函数、接口或类时,不预先指定具体的类型,而是在使用时再指定类型。通过<T>语法来定义泛型参数。

    typescript
     体验AI代码助手
     代码解读
    复制代码
    function identity<T>(arg: T): T {
        return arg;
    }
    let output = identity<string>("myString");
    
  3. 问:解释unknownany的区别

    答:

    • any类型会跳过类型检查,允许任何操作
    • unknown是类型安全的any,必须通过类型断言或类型收窄后才能进行具体操作
    • 使用unknownany更安全,因为它要求在使用前进行类型检查

高级特性

  1. 问:什么是联合类型和交叉类型?

    答:

    • 联合类型(Union)表示值可以是几种类型之一,使用|符号:type MyType = string | number;
    • 交叉类型(Intersection)表示将多个类型合并为一个类型,使用&符号:type Combined = Type1 & Type2;
  2. 问:什么是字面量类型?

    答:字面量类型是一种更具体的类型,表示特定的值而不是广泛的类型:

    typescript
     体验AI代码助手
     代码解读
    复制代码
    type Direction = 'North' | 'East' | 'South' | 'West';
    let dir: Direction = 'North'; // 只能是这四个值之一
    
  3. 问:什么是索引签名?

    答:索引签名用于描述对象的索引类型,允许额外的属性:

    typescript
     体验AI代码助手
     代码解读
    复制代码
    interface StringArray {
      [index: number]: string;
    }
    

高级类型操作

  1. 问:如何在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;
    }
    
  2. 问:什么是类型守卫?如何使用?

    答:类型守卫是一种在运行时检查变量类型的表达式,帮助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;
    }
    
  3. 问:什么是条件类型?

    答:条件类型根据条件选择不同的类型,类似于三元运算符:

    typescript
     体验AI代码助手
     代码解读
    复制代码
    type Check<T> = T extends string ? string : number;
    

实践问题

  1. 问:如何在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];
    
  2. 问:什么是映射类型?Partial和Required有什么作用?

    答:映射类型基于旧类型创建新类型,改变属性:

    • Partial<T>:将所有属性变为可选
    • Required<T>:将所有属性变为必需
    • Readonly<T>:将所有属性变为只读
    • Pick<T, K>:从T中选择一组属性K
    • Omit<T, K>:从T中排除一组属性K
  3. 问:如何处理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;
    }
    

配置与工具

  1. 问:tsconfig.json的主要配置项有哪些?

    答:主要配置项包括:

    • compilerOptions:编译选项

    • include/exclude:指定要编译的文件

    • extends:继承其他配置文件

    • 重要的编译选项:

      • target:编译目标ES版本
      • module:模块系统
      • strict:严格模式
      • esModuleInterop:兼容性设置
      • outDir:输出目录
  2. 问:如何在TypeScript中使用第三方库?

    答:

    • 优先使用有TypeScript类型定义的库
    • 对于没有类型定义的库,可以使用DefinitelyTyped提供的类型定义文件:npm install @types/库名
    • 也可以自己创建声明文件:*.d.ts
  3. 问:如何在TypeScript中处理模块声明?

    答:使用声明文件(.d.ts)来为模块提供类型定义:

    typescript
     体验AI代码助手
     代码解读
    复制代码
    // 为一个JS模块提供声明
    declare module 'my-module' {
      export function myFunction(): void;
      export const myVariable: string;
    }
    

    进阶概念

  4. 问:解释一下TypeScript中的协变与逆变

答:协变与逆变是关于类型兼容性的概念:

  1. 问:什么是可辨识联合类型(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;
  }
}
  1. 问:什么是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

工程实践

  1. 问:如何处理TypeScript中的模块路径别名?

答:在tsconfig.json中配置paths选项:

json
 体验AI代码助手
 代码解读
复制代码
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"]
    }
  }
}

要注意的是,这只处理了TypeScript编译时的路径解析,配合使用Webpack等构建工具的alias配置才能完全支持。

  1. 问:TypeScript中的声明合并是什么?

答:声明合并是指编译器将多个同名声明合并为一个定义。常见于接口合并:

typescript
 体验AI代码助手
 代码解读
复制代码
interface Box {
  height: number;
}
interface Box {
  width: number;
}
// 两个接口会合并为:
// interface Box {
//   height: number;
//   width: number;
// }
  1. 问: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

高级类型操作

  1. 问:如何实现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]
  1. 问:什么是TypeScript中的命名空间(namespace)?它与模块有什么区别?

答:命名空间是一种将代码组织在一个命名容器内的方式:

  • 命名空间(内部模块):使用namespace关键字定义,主要用于组织代码
  • 模块(外部模块):使用ES模块系统的import/export语法,更适合现代应用

现代TypeScript开发推荐使用ES模块系统而非命名空间。

  1. 问:如何在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;
  }
}
  1. 问: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();
  1. 问:解释一下TypeScript中的never类型的使用场景

答:never类型表示永不存在的值的类型,主要用于:

  • 函数抛出异常或无法正常返回值的返回类型
  • 作为联合类型中不可能出现的类型
  • 用于完整性检查,比如确保switch语句涵盖所有可能情况
  1. 问:如何在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(); // 链式调用