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)
相关推荐
Linsk9 小时前
为什么BigInt无法通过Babel降级?
前端·typescript·前端工程化
濮水大叔10 小时前
VonaJS AOP编程:魔术方法
typescript·nodejs·nestjs
Mintopia10 小时前
🧩 TypeScript防御性编程:让Bug无处遁形的艺术
前端·typescript·函数式编程
菜鸟una13 小时前
【微信小程序 + map组件】自定义地图气泡?原生气泡?如何抉择?
前端·vue.js·程序人生·微信小程序·小程序·typescript
孟陬1 天前
面试常见问题 TS 的 infer 你会用吗?对象如何转 snake_case
typescript
还是大剑师兰特1 天前
Scala面试题及详细答案100道(81-90)-- 框架与生态
scala·大剑师·scala面试题
guangzan1 天前
解决 Semi Design Upload 组件实现自定义压缩,上传文件后无法触发 onChange
typescript·semi design
AI智能研究院1 天前
TypeScript 快速入门与环境搭建
前端·javascript·typescript
liangshanbo12152 天前
React + TypeScript 企业级编码规范指南
ubuntu·react.js·typescript