TypeScript 面试题及详细答案 100题 (61-70)-- 泛型(Generics)

前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux... 。

前后端面试题-专栏总目录

文章目录

  • 一、本文面试题目录
      • [61. 什么是泛型?它解决了什么问题?举例说明其核心价值。](#61. 什么是泛型?它解决了什么问题?举例说明其核心价值。)
      • [62. 如何定义泛型函数?如何指定泛型的默认类型?](#62. 如何定义泛型函数?如何指定泛型的默认类型?)
      • [63. 如何定义泛型接口和泛型类?](#63. 如何定义泛型接口和泛型类?)
      • [64. 什么是泛型约束?如何用`extends`实现泛型约束?举例说明。](#64. 什么是泛型约束?如何用extends实现泛型约束?举例说明。)
      • [65. 如何对泛型参数进行"多约束"(即同时满足多个条件)?](#65. 如何对泛型参数进行“多约束”(即同时满足多个条件)?)
      • [66. `keyof`操作符的作用是什么?如何结合泛型使用?](#66. keyof操作符的作用是什么?如何结合泛型使用?)
      • [67. 泛型工具类型`Partial`、`Required`、`Pick`、`Omit`的作用是什么?如何实现一个简易版`Partial`?](#67. 泛型工具类型PartialRequiredPickOmit的作用是什么?如何实现一个简易版Partial?)
      • [68. 什么是泛型的"协变"和"逆变"?`in`关键字如何影响泛型的变异性?](#68. 什么是泛型的“协变”和“逆变”?in关键字如何影响泛型的变异性?)
      • [69. 如何用泛型实现一个"深拷贝"函数的类型定义?](#69. 如何用泛型实现一个“深拷贝”函数的类型定义?)
      • [70. 泛型与联合类型结合时,如何避免"分布式条件类型"的副作用?](#70. 泛型与联合类型结合时,如何避免“分布式条件类型”的副作用?)
  • 二、100道TypeScript面试题目录列表

一、本文面试题目录

61. 什么是泛型?它解决了什么问题?举例说明其核心价值。

  • 原理说明

    泛型(Generics)是 TypeScript 中用于创建可复用、类型安全的组件的工具,它允许在定义函数、接口或类时不预先指定具体类型,而是在使用时动态指定。

    它解决的核心问题是:代码复用与类型约束的矛盾 。没有泛型时,要么通过 any 牺牲类型安全以实现复用,要么为每种类型重复编写逻辑。泛型通过"类型参数"实现了"同一份代码适配多种类型",同时保持类型检查。

  • 示例代码

    typescript 复制代码
    // 不使用泛型:要么丢失类型检查(any),要么重复实现
    function identityAny(arg: any): any {
      return arg;
    }
    const num1: number = identityAny(123); // 实际类型为any,无类型约束
    
    // 使用泛型:复用逻辑且保留类型
    function identity<T>(arg: T): T {
      return arg;
    }
    const num2: number = identity(123); // 正确,T被推断为number
    const str: string = identity("hello"); // 正确,T被推断为string
  • 核心价值

    • 类型安全:避免 any 导致的类型丢失。
    • 代码复用:一份逻辑适配多种类型(如通用工具函数、容器类)。
    • 灵活性:使用时动态指定类型,兼顾扩展性与约束性。

62. 如何定义泛型函数?如何指定泛型的默认类型?

  • 原理说明

    泛型函数通过在函数名后添加 <类型参数> 定义,类型参数可在参数、返回值中使用。

    泛型默认类型允许为类型参数指定默认值,当未显式或隐式指定类型时,使用默认类型。

  • 示例代码

    typescript 复制代码
    // 1. 基础泛型函数定义
    function logAndReturn<T>(value: T): T {
      console.log(value);
      return value;
    }
    logAndReturn<number>(123); // 显式指定T为number
    logAndReturn("hello"); // 隐式推断T为string
    
    // 2. 泛型默认类型(使用 = 指定)
    function createArray<T = string>(length: number, value: T): T[] {
      return Array(length).fill(value);
    }
    createArray(3, "a"); // T默认为string,返回string[]
    createArray<number>(3, 0); // 显式指定T为number,返回number[]

63. 如何定义泛型接口和泛型类?

  • 原理说明

    泛型接口和类通过在名称后添加 <类型参数> 定义,类型参数可用于约束接口的属性/方法或类的成员。

  • 示例代码

    typescript 复制代码
    // 1. 泛型接口
    interface Container<T> {
      value: T;
      setValue: (v: T) => void;
    }
    // 使用时指定类型
    const numContainer: Container<number> = {
      value: 0,
      setValue: (v) => numContainer.value = v
    };
    
    // 2. 泛型类
    class Stack<T> {
      private items: T[] = [];
      push(item: T): void {
        this.items.push(item);
      }
      pop(): T | undefined {
        return this.items.pop();
      }
    }
    const stringStack = new Stack<string>();
    stringStack.push("a"); // 正确
    stringStack.push(123); // 错误:类型number不符合string

64. 什么是泛型约束?如何用extends实现泛型约束?举例说明。

  • 原理说明

    泛型约束(Generic Constraints)用于限制类型参数的范围,确保其包含特定属性或方法,避免在使用泛型时访问不存在的成员。通过 extends 关键字实现,指定类型参数必须满足的条件。

  • 示例代码

    typescript 复制代码
    // 约束T必须包含length属性
    interface HasLength {
      length: number;
    }
    // T extends HasLength:确保T有length属性
    function getLength<T extends HasLength>(arg: T): number {
      return arg.length; // 安全访问length
    }
    
    getLength("hello"); // 正确:string有length
    getLength([1, 2, 3]); // 正确:数组有length
    getLength(123); // 错误:number无length属性

65. 如何对泛型参数进行"多约束"(即同时满足多个条件)?

  • 原理说明

    多约束指类型参数需同时满足多个条件,可通过 extends 结合交叉类型(&)实现,即 T extends A & B & C 表示 T 必须同时符合 A、B、C 的约束。

  • 示例代码

    typescript 复制代码
    interface HasId {
      id: number;
    }
    interface HasName {
      name: string;
    }
    // T必须同时满足HasId和HasName
    function logEntity<T extends HasId & HasName>(entity: T): void {
      console.log(`ID: ${entity.id}, Name: ${entity.name}`);
    }
    
    logEntity({ id: 1, name: "test" }); // 正确:满足两个约束
    logEntity({ id: 1 }); // 错误:缺少name(不满足HasName)
    logEntity({ name: "test" }); // 错误:缺少id(不满足HasId)

66. keyof操作符的作用是什么?如何结合泛型使用?

  • 原理说明
    keyof 用于获取某个类型的所有键名,返回一个由键名组成的联合类型。结合泛型时,可动态约束参数为对象的键,实现类型安全的属性访问。

  • 示例代码

    typescript 复制代码
    // 1. keyof基础用法
    interface User {
      name: string;
      age: number;
    }
    type UserKeys = keyof User; // 等价于 "name" | "age"
    
    // 2. 结合泛型:安全访问对象属性
    function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
      return obj[key]; // 确保key是T的有效键
    }
    
    const user: User = { name: "Alice", age: 30 };
    getProperty(user, "name"); // 正确:返回string
    getProperty(user, "age"); // 正确:返回number
    getProperty(user, "email"); // 错误:"email"不是User的键

67. 泛型工具类型PartialRequiredPickOmit的作用是什么?如何实现一个简易版Partial

  • 原理说明

    泛型工具类型是 TypeScript 内置的基于泛型的类型转换工具,用于快速生成新类型:

    • Partial<T>:将 T 的所有属性转为可选。
    • Required<T>:将 T 的所有属性转为必填(与 Partial 相反)。
    • Pick<T, K>:从 T 中选取键为 K 的属性组成新类型。
    • Omit<T, K>:从 T 中排除键为 K 的属性组成新类型(与 Pick 相反)。
  • 示例代码

    typescript 复制代码
    interface User {
      id: number;
      name: string;
      age: number;
    }
    
    // 内置工具类型示例
    type PartialUser = Partial<User>; // { id?: number; name?: string; age?: number }
    type RequiredUser = Required<PartialUser>; // 恢复为User(所有属性必填)
    type UserName = Pick<User, "name">; // { name: string }
    type UserWithoutAge = Omit<User, "age">; // { id: number; name: string }
    
    // 简易版Partial实现
    type MyPartial<T> = {
      [P in keyof T]?: T[P]; // 遍历T的所有键,转为可选
    };
    type MyPartialUser = MyPartial<User>; // 效果同Partial<User>

68. 什么是泛型的"协变"和"逆变"?in关键字如何影响泛型的变异性?

  • 原理说明

    泛型的"变异性"描述类型参数的子类型关系如何影响泛型类型的子类型关系:

    • 协变(Covariant) :若 A extends B,则 Generic<A> extends Generic<B>(子类型关系保留)。
    • 逆变(Contravariant) :若 A extends B,则 Generic<B> extends Generic<A>(子类型关系反转)。
    • TypeScript 中,默认情况下:
      • 泛型接口/类的属性是协变的。
      • 函数参数是逆变的(在 strictFunctionTypes 开启时)。
    • in 关键字用于标记泛型参数为"逆变位置"(通常用于函数参数),强制其逆变行为。
  • 示例代码

    typescript 复制代码
    // 协变示例(数组是协变的)
    type Animal = { name: string };
    type Dog = Animal & { bark: () => void };
    const dogs: Dog[] = [{ name: "Buddy", bark: () => {} }];
    const animals: Animal[] = dogs; // 正确:Dog[] 是 Animal[] 的子类型(协变)
    
    // 逆变示例(函数参数是逆变的)
    type AnimalHandler = (a: Animal) => void;
    type DogHandler = (d: Dog) => void;
    const animalHandler: AnimalHandler = (a) => console.log(a.name);
    const dogHandler: DogHandler = (d) => d.bark();
    
    // 当strictFunctionTypes开启时:
    const handler: DogHandler = animalHandler; // 正确:AnimalHandler 是 DogHandler 的子类型(逆变)
    const handler2: AnimalHandler = dogHandler; // 错误:DogHandler 不是 AnimalHandler 的子类型

69. 如何用泛型实现一个"深拷贝"函数的类型定义?

  • 原理说明

    深拷贝函数需递归复制对象的所有层级,类型定义需通过泛型递归处理嵌套结构,区分基本类型、数组、对象等。

  • 示例代码

    typescript 复制代码
    type DeepClone<T> = 
      T extends number | string | boolean | null | undefined | symbol | bigint 
        ? T // 基本类型直接返回
        : T extends Array<infer U> 
          ? Array<DeepClone<U>> // 数组:递归处理元素
          : { [K in keyof T]: DeepClone<T[K]> }; // 对象:递归处理属性
    
    function deepClone<T>(value: T): DeepClone<T> {
      if (typeof value !== "object" || value === null) {
        return value as DeepClone<T>;
      }
      if (Array.isArray(value)) {
        return value.map(deepClone) as DeepClone<T>;
      }
      const cloned: Record<string, any> = {};
      for (const key in value) {
        cloned[key] = deepClone(value[key]);
      }
      return cloned as DeepClone<T>;
    }
    
    // 测试
    const obj = { a: 1, b: { c: "hello" }, d: [1, 2] };
    const cloned = deepClone(obj);
    // cloned类型:{ a: number; b: { c: string }; d: number[] }(与原对象类型一致)

70. 泛型与联合类型结合时,如何避免"分布式条件类型"的副作用?

  • 原理说明

    分布式条件类型指:当泛型参数为联合类型 A | B | C 时,条件类型 T extends U ? X : Y 会自动分发为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)。若需避免这种分发行为,可通过将泛型参数包裹在元组中([T] extends [U])阻止分布式处理。

  • 示例代码

    typescript 复制代码
    // 分布式条件类型(默认行为)
    type Distributed<T> = T extends string ? "string" : "other";
    type Result1 = Distributed<string | number>; // "string" | "other"(分发处理)
    
    // 避免分布式行为(用元组包裹)
    type NonDistributed<T> = [T] extends [string] ? "string" : "other";
    type Result2 = NonDistributed<string | number>; // "other"(整体判断,不分发)
    
    // 应用场景:判断类型是否完全匹配联合类型
    type IsExactStringUnion<T> = [T] extends [string] ? true : false;
    type Test1 = IsExactStringUnion<string>; // true
    type Test2 = IsExactStringUnion<string | number>; // false(避免了分发导致的错误判断)

二、100道TypeScript面试题目录列表

文章序号 TypeScript面试题100道
1 TypeScript面试题及详细答案100道(01-10)
2 TypeScript面试题及详细答案100道(11-20)
3 TypeScript面试题及详细答案100道(21-30)
4 TypeScript面试题及详细答案100道(31-40)
5 TypeScript面试题及详细答案100道(41-50)
6 TypeScript面试题及详细答案100道(51-60)
7 TypeScript面试题及详细答案100道(61-70)
8 TypeScript面试题及详细答案100道(71-80)
9 TypeScript面试题及详细答案100道(81-90)
10 TypeScript面试题及详细答案100道(91-100)
相关推荐
jonjia1 天前
模块、脚本与声明文件
typescript
jonjia1 天前
配置 TypeScript
typescript
jonjia1 天前
TypeScript 工具函数开发
typescript
jonjia1 天前
注解与断言
typescript
jonjia1 天前
IDE 超能力
typescript
jonjia1 天前
对象类型
typescript
jonjia1 天前
快速搭建 TypeScript 开发环境
typescript
jonjia1 天前
TypeScript 的奇怪之处
typescript
jonjia1 天前
类型派生
typescript
jonjia1 天前
开发流程中的 TypeScript
typescript