TypeScript - 构造函数接口:没有匹配构造函数的类仍然可以赋值
想象一下,我们有一个接口,它定义了构造函数应该接受哪些参数:
interface Ctr {
new (num: number): any;
}
然后我们有一个带默认构造函数的类:
class DefCtr {
}
然后,不知出于什么原因,我可以将 DefCtr 类分配给类型为 Ctr 的变量:
const Instance: Ctr = DefCtr;
const i = new Instance(1);
console.log(i);
// The output is:
// DefCtr {}
所以 Instance 是一个 DefCtr 类(它没有接受数字的构造函数)。但是接口在我创建它的实例时强制要求我传递一个数字(该数字将被忽略)。
有趣的是,这只有在类有默认构造函数时才有效。例如,这将不起作用:
interface Ctr {
new (num: number): any;
}
class NotDefCtr {
// Lets create a non-default constructor
constructor(str: string) {
}
}
const Instance: Ctr = NotDefCtr; // Error:
// Type 'typeof NotDefCtr' is not assignable to type 'Ctr'.
// Types of parameters 'str' and 'num' are incompatible.
// Type 'number' is not assignable to type 'string'.
错误是有道理的。但从我的角度来看,当尝试分配一个带有默认构造函数的类时收到错误也是有道理的(因为它没有其他与接口匹配的构造函数)。
答案#1
TypeScript 常见问题解答中有一条条目:为什么参数较少的函数可以赋值给接受更多参数的函数?另请参阅手册中关于函数类型兼容性的文档。这是同样的问题,只是将可调用对象换成了可实例化对象。我将把该条目翻译到这个情况:
这是预期和期望的行为。有一个可替换性原则,它说明如果对象X可以替代某个对象Y使用,那么X就是子类型或可赋值给Y的。
在这种情况下,DefCtr 是 Ctr 的子类型,因为 DefCtr 的构造函数可以安全地忽略额外的 number 参数。但 NotDefCtr 不是 Ctr 的子类型,因为 NotDefCtr 的类型表明它期望一个 string 类型的参数;number 不能安全地用作 string,所以它不兼容。
(顺便说一下,事实证明 NotDefCtr 的构造函数实际上会忽略它的输入,因此在运行时不会发生任何问题。但类型系统不关心构造函数的具体实现;它只看到签名——一个契约,说明 NotDefCtr 的构造函数有权期望一个 string 类型的参数。因此用 NotDefCtr 替代 Ctr 仍然是错误的。)