TypeScript 面试题及详细答案 100题 (61-70)-- 泛型(Generics)
《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux… 。
文章目录
- 一、本文面试题目录
- 61. 什么是泛型?它解决了什么问题?举例说明其核心价值。
- 62. 如何定义泛型函数?如何指定泛型的默认类型?
- 63. 如何定义泛型接口和泛型类?
- 64. 什么是泛型约束?如何用`extends`实现泛型约束?举例说明。
- 65. 如何对泛型参数进行“多约束”(即同时满足多个条件)?
- 66. `keyof`操作符的作用是什么?如何结合泛型使用?
- 67. 泛型工具类型`Partial`、`Required`、`Pick`、`Omit`的作用是什么?如何实现一个简易版`Partial`?
- 68. 什么是泛型的“协变”和“逆变”?`in`关键字如何影响泛型的变异性?
- 69. 如何用泛型实现一个“深拷贝”函数的类型定义?
- 70. 泛型与联合类型结合时,如何避免“分布式条件类型”的副作用?
- 二、100道TypeScript面试题目录列表
一、本文面试题目录
61. 什么是泛型?它解决了什么问题?举例说明其核心价值。
-
原理说明:
泛型(Generics)是 TypeScript 中用于创建可复用、类型安全的组件的工具,它允许在定义函数、接口或类时不预先指定具体类型,而是在使用时动态指定。
它解决的核心问题是:代码复用与类型约束的矛盾。没有泛型时,要么通过any
牺牲类型安全以实现复用,要么为每种类型重复编写逻辑。泛型通过“类型参数”实现了“同一份代码适配多种类型”,同时保持类型检查。 -
示例代码:
// 不使用泛型:要么丢失类型检查(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. 如何定义泛型函数?如何指定泛型的默认类型?
-
原理说明:
泛型函数通过在函数名后添加<类型参数>
定义,类型参数可在参数、返回值中使用。
泛型默认类型允许为类型参数指定默认值,当未显式或隐式指定类型时,使用默认类型。 -
示例代码:
// 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. 如何定义泛型接口和泛型类?
-
原理说明:
泛型接口和类通过在名称后添加<类型参数>
定义,类型参数可用于约束接口的属性/方法或类的成员。 -
示例代码:
// 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
关键字实现,指定类型参数必须满足的条件。 -
示例代码:
// 约束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 的约束。 -
示例代码:
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
用于获取某个类型的所有键名,返回一个由键名组成的联合类型。结合泛型时,可动态约束参数为对象的键,实现类型安全的属性访问。 -
示例代码:
// 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. 泛型工具类型Partial
、Required
、Pick
、Omit
的作用是什么?如何实现一个简易版Partial
?
-
原理说明:
泛型工具类型是 TypeScript 内置的基于泛型的类型转换工具,用于快速生成新类型:Partial<T>
:将 T 的所有属性转为可选。Required<T>
:将 T 的所有属性转为必填(与Partial
相反)。Pick<T, K>
:从 T 中选取键为 K 的属性组成新类型。Omit<T, K>
:从 T 中排除键为 K 的属性组成新类型(与Pick
相反)。
-
示例代码:
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
关键字用于标记泛型参数为“逆变位置”(通常用于函数参数),强制其逆变行为。
- 协变(Covariant):若
-
示例代码:
// 协变示例(数组是协变的) 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. 如何用泛型实现一个“深拷贝”函数的类型定义?
-
原理说明:
深拷贝函数需递归复制对象的所有层级,类型定义需通过泛型递归处理嵌套结构,区分基本类型、数组、对象等。 -
示例代码:
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]
)阻止分布式处理。 -
示例代码:
// 分布式条件类型(默认行为) 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) |