抽象构造签名和具有类型层次结构的实例化



给定以下代码:

abstract class Base {
abstract getName(): string;
printName() {
console.log("a")
}
}
class Derived extends Base {
getName() {
return "";
}
}

以下三种情况中的每一种都至少有一个错误。但是我可以运行它。为什么?

下面代码的功能是什么?这是正确的方式吗?

此处有1个链接

function greet(ctor: Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);
greet(Base)

2链接此处

function greet(ctor: typeof Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);
greet(Base);

3链接此处

function greet(ctor: new () => Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);
greet(Base);

https://www.typescriptlang.org/docs/handbook/2/classes.html#abstract-构造签名

以下三种情况中的每一种都至少有一个错误。但是我可以运行它。为什么?

除非启用--noEmitOnError编译器标志,否则即使存在类型错误,编译器仍将发出JavaScript。这个JavaScript代码看起来非常像静态类型系统的TypeScript代码已经被删除(并且可能会降级)。静态类型系统的特性(如接口定义和类型注释)不会影响运行时行为。你的所有三种场景都会像一样发射

function greet(ctor) { // type annotation erased
const instance = new ctor();
instance.printName();
}
greet(Derived);
greet(Base);

并且因此在运行时将起相同的作用。TypeScript保留JavaScript的运行时行为。


下面代码的功能是什么?有正确的方法吗?

这三种场景之间的差异发生在类型级别,而不是发出的JavaScript。虽然它们在运行时的行为相同,但它们向编译器传达了不同的意图。这些错误意味着它们在某种程度上都是不正确的:代码与其自己声明的目的相冲突,编译器会告诉你哪里出了问题。";正确的";这个方法和你们的实际意图有关,所以它是主观的。


让我们看看第一个:

// version 1
function greet(ctor: Base) {
const instance = new ctor(); // error! Base has no construct signatures
instance.printName();
}
greet(Derived); // error! typeof Derived is not a Base
greet(Base); // error! typeof Base is not a Base

这是最不正确的。您已经告诉编译器ctorBase类的实例,而不是构造函数。因此,当您编写new ctor()时,编译器会警告您,您似乎正在尝试构造可能没有构造签名的东西。调用greet(Derived)greet(Base)都是错误的,因为DerivedBase类构造函数都不是Base实例

您似乎不太可能真正希望ctor是一个实例,因为您使用它就像使用构造函数一样。


好的,现在是第二个:

// version 2
function greet(ctor: typeof Base) {
const instance = new ctor(); // error! Cannot construct abstract class
instance.printName();
}
greet(Derived);
greet(Base);

这稍微好一点;您告诉编译器ctor与名为Base的值的类型相同。。。也就是说,它是一个抽象类构造函数,其具体子类可以构造Base的实例。您可以将CCD_ 14构造函数看作";不可构造的构造函数";。。。它可以像类构造函数一样具有static属性,但您不应该直接尝试在它上调用new

无论如何,现在您可以调用greet(Derived)greet(Base),因为具体构造函数可以分配给抽象构造函数(但不能反之亦然)。这是因为你可以避免建造混凝土建造师。

但在CCD_ 19的实现中存在一个问题。您告诉编译器ctor是抽象的,但随后您继续调用new ctor();。这是个错误。这里的修复方法是更改ctor的类型,或者避免调用new ctor()。我想你想打电话给new ctor(),所以我们将继续:


最后:

// version 3
function greet(ctor: new () => Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);
greet(Base); // error! Abstract constructor is not newable

这是最接近正确的。通过给ctor类型new () => Base,你就可以说ctor是零参数的可构造。这显式地允许您在没有错误的情况下调用new ctor();。对greet(Derived)的调用很好,因为Derived实际上是一个不带任何参数的具体类构造函数。

此处的错误出现在对greet(Base)的调用中。Base是不可构造的,因此编译器会警告您。在这里正确的做法是不要调用greet(Base)。注释掉或删除。


我将回到问题

我可以运行它。为什么?

答案略有不同。在不更改版本3的类型的情况下,让我们想象一下,如果我们更改了greet():实现中的第二行

// version 4
function greet(ctor: new () => Base) {
const instance = new ctor();
console.log(instance.getName());
}

这里没有编译器错误。由于ctor应该是一个具体的构造函数,它将构造Base的一个具体实例,该实例必须有一个返回stringgetName()方法。呼叫instance.getName()应该没问题,对吧?

greet(Derived); // ""

好的,到目前为止。但是怎么样:

greet(Base); // compiler error 
// RUNTIME ERROR! instance.getName is not a function

这是问题。您以前能够运行greet(Base),因为greet()实现恰好不依赖于抽象构造函数和具体构造函数之间的任何差异。但现在你明白了编译器抱怨的原因:你在一个需要具体构造函数的地方使用了一个抽象构造函数。。。这可能导致运行时错误。greet()的类型签名是(ctor: new () => Base) => void,表示调用者和实现者之间的契约。实现者将只使用它所知道的ctor的特性,而调用者将只传入具有这些特性的ctor。如果调用者违反了约定,那么实现者就没有过错。


所以这个代码的正确版本大概是不调用greet(Base):

function greet(ctor: new () => Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);

到代码的游乐场链接

相关内容

  • 没有找到相关文章

最新更新