编程前端/全栈开发

TypeScript 中构造函数的类型化是如何工作的,继承时可能会出现哪些困难?

用 Hintsage AI 助手通过面试

答案。

问题的历史

最初的 JavaScript 没有严格的类和构造函数类型化,这导致了运行时错误。TypeScript 添加了类型系统以便于更安全的编程,并支持带继承的构造函数类型化,这对于大型应用程序的开发非常重要。

问题

TypeScript 中的构造函数类型化需要同时考虑构造函数的签名、创建的实例类型和继承的特性。当基类和派生类中的构造函数签名不一致时,或者当只对返回值进行了类型化而忽略了构造函数的输入参数时,就会出现问题。

解决方案

在 TypeScript 中,可以通过特殊签名显式地对构造函数进行类型化,使用表达式 new (...args: any[]) => T。在继承中,重要的是要保持签名的一致性,并正确扩展基类。

代码示例:

class Animal { constructor(public name: string) {} } class Dog extends Animal { constructor(name: string, public breed: string) { super(name); } } // 构造函数类型 function createInstance<T>(C: new (...args: any[]) => T, ...args: any[]): T { return new C(...args); } const dog = createInstance(Dog, 'Rex', '拉布拉多');

关键特性:

  • 构造函数签名是一个独立的实体,通过 new 声明
  • 基类和派生类构造函数参数的兼容性
  • 通过类型化构造函数实现通用实例的创建

误导性问题。

可以在类中声明多个构造函数吗,像在 Java 或 C# 中那样?

不可以,TypeScript 不支持多个构造函数。为了模拟重载,使用带有单一实现的签名重载。正确的方法:

class Example { constructor(x: string); constructor(x: number); constructor(x: number | string) { // 单一实现 } }

可以只对构造函数的返回类型进行类型化,而忽略参数吗?

不可以,构造函数的签名必须包含参数。正确的类型化示例:

interface Constructable<T> { new (...args: any[]): T; }

如果在子类中声明构造函数而不调用 super,会发生什么?

会出现编译错误:子类的构造函数必须在访问 this 之前调用 super。

常见错误和反模式

  • 基类和派生类构造函数的不一致参数
  • 子类构造函数中缺少对 super 的调用
  • 在通过工厂抽象时构造函数的错误类型化

生活中的示例

消极案例

在项目中使用基类 Animal 及其构造函数(name),而在继承的 Dog 中添加了(name, breed),但忘记正确扩展签名。

优点:

  • 记录新的参数

缺点:

  • 通过通用工厂创建实例时不兼容,编译阶段出现错误

积极案例

构造函数类型单独提取,工厂 createInstance 通过 CorrectConstructable<T> 参数化,签名保持一致。

优点:

  • 类型安全,可预见的行为
  • 易于编写通用函数

缺点:

  • 需要更仔细地处理类型化