给定以下代码:
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
这是最不正确的。您已经告诉编译器ctor
是Base
类的实例,而不是构造函数。因此,当您编写new ctor()
时,编译器会警告您,您似乎正在尝试构造可能没有构造签名的东西。调用greet(Derived)
和greet(Base)
都是错误的,因为Derived
和Base
类构造函数都不是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
的一个具体实例,该实例必须有一个返回string
的getName()
方法。呼叫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);
到代码的游乐场链接