面试题答案
一键面试接口(Interface)
- 优势:
- 可拓展性:在大型项目中,接口可以通过
extends
关键字实现继承和拓展。例如,在一个电商项目里,有基础的Product
接口定义商品基本信息,后续可以通过extends Product
拓展出ElectronicsProduct
接口,添加电子产品特有的属性如warrantyPeriod
。这样在代码重构时,很容易对相关类型进行扩展而不影响其他部分。 - 类型检查严格:接口定义的结构必须严格匹配,这在多层模块结构中可以确保模块间交互数据的准确性。例如在模块间传递用户信息时,定义
User
接口,确保传递的数据对象拥有正确的属性和类型,避免因数据结构不一致导致的运行时错误。 - 适用于面向对象编程:在使用第三方库如 React 进行前端开发时,React 组件常常使用接口来定义
props
和state
的类型。如定义一个Button
组件,通过接口来定义props
中的text
、onClick
等属性类型,符合面向对象编程的思想,使得代码结构清晰。
- 可拓展性:在大型项目中,接口可以通过
- 劣势:
- 不能用于原始类型:接口不能直接用于定义原始类型的别名,例如不能定义
interface StringAlias = string
,相比之下类型别名则可以。 - 合并规则复杂:当有多个同名接口时,会进行合并。虽然在某些情况下很有用,但如果不小心定义了重复但不兼容的接口,排查错误会比较困难。例如在不同模块中都定义了
User
接口且属性不一致,合并时可能会出现意料之外的结果。
- 不能用于原始类型:接口不能直接用于定义原始类型的别名,例如不能定义
类型别名(Type Alias)
- 优势:
- 灵活性:类型别名不仅可以用于对象类型,还能用于原始类型、联合类型、交叉类型等。在处理函数重载时非常方便,比如定义一个函数可以接受字符串或者数字作为参数,
type StringOrNumber = string | number; function printValue(value: StringOrNumber) {...}
。在频繁重构代码时,如果参数类型发生变化,修改类型别名一处即可。 - 简洁性:对于一些简单的类型定义,类型别名更加简洁。例如定义一个表示颜色的类型别名
type Color = 'red' | 'green' | 'blue';
,相比使用接口定义更加直观。 - 可以使用泛型:类型别名支持泛型,在处理一些通用的数据结构时很有用。比如定义一个通用的
Maybe
类型,表示可能为null
或某种类型的值,type Maybe<T> = T | null;
。
- 灵活性:类型别名不仅可以用于对象类型,还能用于原始类型、联合类型、交叉类型等。在处理函数重载时非常方便,比如定义一个函数可以接受字符串或者数字作为参数,
- 劣势:
- 不可拓展:类型别名一旦定义不能像接口那样通过继承拓展。例如已经定义了
type Point = { x: number; y: number; }
,如果想添加新属性,无法直接拓展,只能重新定义一个新的类型别名。 - 对象字面量类型检查宽松:当使用类型别名定义对象字面量类型时,类型检查相对接口没有那么严格,可能会允许一些额外属性存在,在模块间数据交互严格要求时可能产生隐患。
- 不可拓展:类型别名一旦定义不能像接口那样通过继承拓展。例如已经定义了
合理选型建议
- 模块间数据交互:在多层模块结构中,模块间传递复杂对象数据时,优先使用接口。例如在一个后端服务模块向前端展示模块传递用户详细信息时,使用接口定义用户信息结构,确保数据准确传递。
- 简单类型定义:对于原始类型、联合类型、交叉类型等简单类型定义,使用类型别名。比如在一个工具模块中定义一个函数接受文件类型的参数
type FileType = 'jpg' | 'png' | 'pdf';
,简单明了。 - 第三方库集成:如果第三方库使用接口来定义类型(如 React 的
props
定义),遵循库的习惯使用接口。如果库没有明确偏好,根据具体场景选择,例如处理库中函数的复杂参数类型时,若涉及联合类型等,使用类型别名更合适。 - 频繁重构场景:如果预计代码会频繁重构,特别是参数类型、返回值类型等可能发生变化的函数,使用类型别名,方便统一修改。例如在一个数据处理模块中,函数可能会随着业务变化而调整输入输出类型,使用类型别名可以快速更新。