面试题答案
一键面试内存占用
- 类型别名(type alias):类型别名本质上只是给类型起了一个新名字,在编译后不会产生额外的内存占用,因为它仅仅是类型层面的概念,不影响运行时的代码。
- 接口(interface):接口同样只存在于编译时,在运行时不会产生任何代码,所以也不额外占用内存。从内存占用角度看,二者没有区别,都不影响运行时的内存情况。
编译优化
- 类型别名(type alias):
- 类型别名可以用于定义联合类型、交叉类型等复杂类型,例如
type MyType = string | number
。当使用复杂类型别名时,编译器需要处理这些类型组合,对于复杂联合类型或交叉类型的解析可能相对复杂一些。 - 类型别名还可以是泛型的,
type Func<T> = (arg: T) => T
,编译器在处理泛型类型别名时,需要根据实际传入的类型参数进行实例化,这在一定程度上增加了编译的工作量。
- 类型别名可以用于定义联合类型、交叉类型等复杂类型,例如
- 接口(interface):
- 接口主要用于定义对象类型结构,编译器对接口的处理相对较为直接,它主要检查对象是否符合接口定义的结构。例如
interface MyInterface { prop: string }
,编译器只需验证对象是否有prop
属性且类型为string
。 - 接口也支持继承,
interface ChildInterface extends ParentInterface {}
,编译器在处理继承关系时,会将子接口和父接口的结构进行合并检查,相对清晰明了。整体而言,在编译优化方面,对于简单对象结构的定义,接口的编译效率可能略高;但对于复杂类型组合,类型别名更具优势。
- 接口主要用于定义对象类型结构,编译器对接口的处理相对较为直接,它主要检查对象是否符合接口定义的结构。例如
扩展性
- 类型别名(type alias):
- 类型别名一旦定义,不能被重新打开进行扩展。例如定义了
type MyType = { prop: string }
后,无法在其他地方再次对MyType
进行扩展添加新属性。 - 不过类型别名可以通过交叉类型实现类似扩展的效果,如
type ExtendedType = MyType & { newProp: number }
,但这并非真正意义上的扩展原类型别名。
- 类型别名一旦定义,不能被重新打开进行扩展。例如定义了
- 接口(interface):
- 接口具有可扩展性,即可以在不同地方多次定义同名接口,编译器会将它们合并。例如:
interface MyInterface { prop: string }
interface MyInterface { newProp: number }
// 此时MyInterface具有prop和newProp属性
- 这种扩展性在大型项目中,不同模块可能需要对同一个接口进行补充定义时非常有用。
实际项目场景选择
- 优先选择类型别名的场景:
- 复杂类型组合:当需要定义联合类型、交叉类型或函数类型等复杂类型时,优先使用类型别名。例如在定义一个函数,它可以接受字符串或数字作为参数时,
type MyFunc = (arg: string | number) => void
。 - 泛型类型:如果需要定义泛型类型,类型别名更合适。如
type ArrayMapper<T> = (arr: T[]) => T[]
,泛型类型别名在函数式编程场景中经常用到。
- 复杂类型组合:当需要定义联合类型、交叉类型或函数类型等复杂类型时,优先使用类型别名。例如在定义一个函数,它可以接受字符串或数字作为参数时,
- 优先选择接口的场景:
- 对象类型定义:当主要是定义对象类型结构时,接口是更好的选择。比如定义一个用户对象接口
interface User { name: string; age: number }
,清晰简洁,并且方便在不同模块中对User
接口进行扩展。 - 可扩展性需求:如果预计在项目的不同部分需要对某个类型进行扩展,优先使用接口。例如在一个大型应用的不同模块中,可能会对
Error
接口进行扩展添加特定模块的错误信息字段。
- 对象类型定义:当主要是定义对象类型结构时,接口是更好的选择。比如定义一个用户对象接口