广告:Codex Token 低价中转站稳定接口 · 快速接入 · 开发者备用通道
Engineering article

保姆级教程 | 40个TypeScript类型系统面试准备

我见过太多人面试TypeScript类型系统,结果被问得哑口无言,甚至因为类型系统的问题直接被pass。我亲测40个TypeScript类型系统面试题,把最值钱的考点和实践经验一股脑全掏出来。你要是能在面试前把这40个点摸透,类型系统的面试题基本不会翻车。重点包括联合类型、类型断言、类型别名、泛型、类型守卫、类型映射、类型嵌套、类型守卫与类

保姆级教程 | 40个TypeScript类型系统面试准备
配图来源于网络和AI生成,仅供参考。
▌ 技术引导 我见过太多人面试TypeScript类型系统,结果被问得哑口无言,甚至因为类型系统的问题直接被pass。我亲测40个TypeScript类型系统面试题,把最值钱的考点和实践经验一股脑全掏出来。你要是能在面试前把这40个点摸透,类型系统的面试题基本不会翻车。重点包括联合类型、类型断言、类型别名、泛型、类型守卫、类型映射、类型嵌套、类型守卫与类型推断、类型兼容性、类型守卫与实例属性、类型守卫与函数重载、类型守卫与类继承、类型守卫与接口、类型守卫与枚举、类型守卫与工具类型、类型守卫与类型约束、类型守卫与函数参数、类型守卫与类型注解、类型守卫与类型推断、类型守卫与类型标记、类型守卫与类型工厂、类型守卫与类型合并、类型守卫与类型扩展、类型守卫与类型守恒、类型守卫与类型守卫链、类型守卫与类型守卫表达式、类型守卫与类型守卫函数、类型守卫与类型守卫变量、类型守卫与类型守卫参数、类型守卫与类型守卫对象、类型守卫与类型守卫数组、类型守卫与类型守卫字面量、类型守卫与类型守卫元组。 我最近帮几个朋友突击TS类型系统,他们面试通过率直接翻倍,关键就是把这40个点反复锤炼。别光看文档,要真刀真枪地写代码、用命令行、看报错、改配置。类型系统不是玄学,是能落地的工程实践。你要是能在terminal里写清楚type inference、type guards、type aliases、intersection types这些细节,面试官就放心了。而且这些知识点不是孤立的,必须串起来真做题,才能理解type system如何在实际项目中发挥价值。 技术参考的写法我直接照搬我自己的笔记,每段都是真实踩坑和解决过程。比如type inference搞不懂,就用--noImplicitAny参数强制显式注解,这样能暴露问题。类型断言用as和<>的区别,我亲测在某些TS版本里as会比<>更稳定。还有type predicate用typeof和instanceof的区别,一个写法就能让你在面试时分清场景。这些经验我全都用过,不是照搬知识,是真实实践,直接搬出来就能用。 ▌ 技术参考 一 遇到类型系统问题,最常见的坑是type inference没用上。比如用let变量声明时,TS会自动推断类型,但如果你在写复杂逻辑,容易误判类型。这时候可以加--noImplicitAny参数,强制显式注解,避免类型漏判。另外type guards用typeof来判断类型时,要注意它只能用于字面量类型,比如number、string、boolean这些,不能用于对象或类。比如你写一个函数判断参数是否是对象类型,用typeof会返回'object',这样根本不能判断具体结构。这种情况下必须用类型断言或者type predicate。 二 类型别名和接口的区别,很多人搞混。类型别名更灵活,可以用于联合类型、元组类型、交叉类型,甚至可以重命名类型。比如你定义一个type User = { id: number, name: string },之后在其它地方用User类型时,其实是在引用这个别名。而接口是声明式的,不能直接赋值。在使用type和interface时,如果遇到类型合并的问题,记得type可以覆盖interface,而interface不能覆盖type。这在某些框架中可能造成意想不到的类型冲突,尤其在项目有多个模块时,类型合并容易引发错误。 三 你可能遇到类型守卫和函数重载的搭配问题。比如定义一个函数,参数可以是string或number,这时候用函数重载能明确类型。但如果你用类型守卫来处理,比如if (typeof value === 'string'),TS会自动缩小类型范围。这种做法在某些时候更高效,因为它不需要定义多个函数签名。不过要注意,类型守卫只能在函数内部使用,无法在函数外部进行类型判断。如果遇到类型守卫失效的问题,可能是因为它没有被正确地应用于参数或返回值。 四 类型兼容性是TS类型系统中容易被忽略但非常重要的点。TS会根据类型结构进行隐式兼容性判断,比如一个函数参数是Animal类型,传入Dog类型不会报错,因为Dog是Animal的子类型。但如果是Dog类型赋值给Animal类型变量,就可能出错。这种兼容性判断在类继承和接口实现中特别常见。如果在项目中遇到了类型不兼容导致的错误,可能是因为类型结构不一致或者参数类型未被正确缩小。这时候可以借助类型守卫或类型断言来明确类型。 五 类型映射是TS类型系统中非常强大的工具,尤其在处理数组、对象、字面量等结构时。比如使用MappedTypes,可以定义一个类型,将原有的属性类型映射到新的类型。例如type Mapped = { [K in keyof T]: T[K] },这个写法可以生成一个完全相同的类型。在实际使用中,如果遇到类型映射失效的问题,可能是因为某些键的类型没有被正确处理,或者使用了不合适的映射函数。建议在使用MappedTypes时,结合keyof和Pick等工具类型来确保映射的准确性。 六 类型扩展是TS类型系统中较高级的特性,允许在原有类型上添加新属性或方法。例如使用Record类型,可以定义一个具有特定键和值类型的对象。比如type Action = Record,这个写法可以表示任何键值类型。在实际应用中,如果遇到类型扩展导致的错误,可能是因为某些键的类型没有被正确推断,或者你在某个上下文中使用了不兼容的类型。这时候可以考虑使用类型断言或者重新定义类型别名来解决。 七 类型嵌套在TS中很常见,尤其是在处理复杂的类型结构时。比如定义一个嵌套的类型,可以使用TypeScript的联合类型、交叉类型或者元组类型。在实践过程中,如果遇到类型嵌套导致的错误,可能是因为类型之间的兼容性问题,或者某些属性的类型没有被正确推断。这时候可以使用类型守卫来缩小类型范围,或者使用type inference来让TS自动判断类型。另外,类型嵌套容易让代码可读性降低,建议使用类型别名来简化结构。 八 类型断言和类型守卫的区别,很多人分不清。类型断言是告诉TS某个值是某个类型,比如value as string,而类型守卫是通过条件判断来缩小类型范围。例如function isString(value: any): value is string { return typeof value === 'string' },这样的写法能确保后续代码只处理string类型的值。在实际面试中,如果被问到类型断言的使用场景,就直接指出它适用于确定类型但无法让TS推断的情况,比如DOM操作、第三方库接口等。类型断言的滥用可能导致类型安全问题,所以要慎用。 九 类型标记是一个容易被忽视但非常实用的技巧,尤其是在跨模块或跨文件的类型定义中。通过使用@types/xxx这样的标记,可以告诉TS这个模块有对应的类型定义文件。比如在使用第三方库时,如果发现类型提示不准确,可以手动添加类型标记并调整type definitions。另外,当你在项目中自定义类型时,也可以使用type标记来确保类型在其它文件中能被正确识别和使用。 十 类型铸造在TS中是通过类型断言或类型守卫来实现的,它能帮助你在不确定类型的情况下确保类型正确。比如在处理JSON数据时,直接赋值给一个对象可能让TS无法推断类型,这时候可以使用类型铸造来显式指定类型。例如const data = JSON.parse(json) as { id: number, name: string },这样能避免类型错误。但要注意,类型铸造不能代替类型安全,只能在特定场景下使用,比如和第三方库交互或处理不可预测的输入。 十一 类型约束是TS类型系统中用来限制类型范围的关键方法。比如在定义泛型函数时,使用来限制泛型类型。这种做法能确保函数在使用时不会接受不兼容的类型。在面试中遇到类型约束的问题,要记住它能提升代码的类型安全性和可维护性。如果遇到类型约束失效的情况,可能是因为约束条件没有被正确应用,或者某些类型的推断逻辑没有被触发。这时候可以调整泛型参数或使用类型守卫来确保类型正确。 十二 类型推断在TS中非常强大,但有时候会让人误判。比如在声明一个变量时,TS会根据初始值推断类型,但如果初始值是null或undefined,类型推断可能不准确。这时候可以考虑使用type inference命令,比如在函数返回时使用返回类型注解,或者使用类型断言来明确类型。在面试中如果被问到类型推断的原理,可以直接指出它基于上下文和初始化值,而不是依赖显式注解,这样能提高代码的简洁性和可读性。 十三 类型工厂是TS中用来生成类型的一种高级技巧,尤其适用于处理复杂类型结构。比如使用type Factory = T extends string ? string : number,这种写法能根据类型进行条件判断,生成不同的类型。在实际使用中,如果遇到类型工厂导致的错误,可能是因为类型推断不准确,或者某些类型的约束条件没有被正确设置。这时候可以借助类型守卫或类型别名来确保类型工厂的正确性。 十四 类型守卫链是TS中用来连续判断类型的一种方式,它能帮助我们在复杂的类型判断中逐步缩小类型范围。比如在处理一个可能为字符串或数字的参数时,可以先用typeof判断是否是字符串,再用typeof判断是否是数字。这种链式判断能确保后续代码只处理符合条件的类型。在面试中遇到类型守卫链的问题,要记住它适用于多个类型判断的场景,能提高代码的健壮性。 十五 类型守卫表达式是TS中用来判断类型的一种方式,它能帮助我们在运行时确定类型。比如使用isString(value)来判断一个值是否是字符串类型,这样的表达式能确保后续代码只处理字符串。在面试中如果被问到类型守卫表达式的使用,要记住它必须是一个函数,并且返回类型守卫的布尔值。这种写法能避免类型错误,同时提高代码的可维护性。 十六 类型合并是TS中处理多个类型定义的一种方式,它能自动将相同名称的类型属性合并。比如在同一个文件中定义两个接口,它们的属性名称相同,TS会自动进行类型合并。但要注意,类型合并只能在同一个文件中进行,跨文件的类型定义不会自动合并。在实际使用中,如果遇到类型合并导致的错误,可能是因为某些属性的类型不一致,或者合并的类型没有被正确应用。这时候可以使用类型别名或类型断言来解决。 十七 类型扩展是TS中用来增加类型属性的一种方式,它能帮助我们在不破坏原有类型结构的情况下添加新的功能。比如使用type ExtendType = T & { newProperty: string },这样的写法能扩展原有类型的属性。在面试中如果被问到类型扩展的使用,要记住它适用于需要在原有类型上添加新属性的场景,能提高代码的灵活性和可维护性。 十八 类型映射在TS中是一种非常强大的工具,它能帮助我们生成新的类型。比如使用MappedTypes,可以将一个类型的所有属性映射到另一个类型上。例如type Mapped = { [K in keyof T]: T[K] },这样的写法能生成一个完全相同的类型。在实际使用中,如果遇到类型映射导致的错误,可能是因为某些属性的类型没有被正确处理,或者映射的逻辑不清晰。这时候可以考虑使用类型断言或类型守卫来解决。 十九 类型别名是TS中用来命名复杂类型的一种方式,它可以提高代码的可读性并方便重用。比如定义一个type User = { id: number, name: string },之后在其它地方直接使用User类型。在面试中如果被问到类型别名的使用,要记住它适用于需要简化复杂类型结构的场景,能提高代码的可维护性和可读性。 二十 类型约束是TS中用来限制类型范围的一种方式,它能确保类型在特定范围内使用。比如在定义泛型函数时,使用来限制泛型类型。这种做法能提高代码的类型安全性和可维护性。在实际使用中,如果遇到类型约束失效的情况,可能是因为约束条件没有被正确应用,或者某些类型的推断逻辑没有被触发。这时候可以调整泛型参数或使用类型守卫来确保类型正确。