问题描述
给定以下 TypeScript 代码:
interface A { /* ... */ }
interface B extends A { /* ... */ }
type Mystery = A & B;
Mystery
类型的对象应该具有 A
的所有属性和 B
的所有属性,因为它是一个交集类型。 B
类型的对象应该已经具有 A
的所有属性,因为 B extends A
。
根据这些定义,类型 Mystery
和类型 B
之间有区别吗?
解决方法
首先近似,如果 B extends A
,那么 A & B
和 B
是相同的。编译器甚至会考虑 A & B
和 B
可以相互赋值:
function foo<A,B extends A>(u: B,m: A & B) {
u = m; // okay
m = u; // okay
}
虽然不一样:
function foo<A,m: A & B) {
var v: B;
var v: A & B; // error,not considered identical
}
在实践中,会出现编译器以不同方式处理 A & B
和 B
的情况。其中一些是被视为错误或设计限制的编译器实现细节;我得去挖掘这些。
但是 A & B
和 B
很容易不同的一个特定地方与调用签名的交集如何解释为可以以多种方式调用的 overloaded 函数有关,而带有调用签名的接口扩展倾向于覆盖父接口调用签名,并且只能以一种方式调用。例如:
interface A { method(p: any): void }
interface B extends A { method(): void }
这是允许的,因为 functions of fewer parameters are assignable to functions of more parameters。类型 B
只看到零参数方法,因此您会得到以下行为:
declare const a: A;
a.method(0); // okay
declare const b: B;
b.method(); // okay
b.method(0); // error!
由于 method()
中的 B
被覆盖,没有参数的 b.method()
是一个错误(即使零参数方法可以分配给多参数方法,你仍然不能故意在没有警告的情况下调用参数过多的函数)。
将此与交集进行比较:
type Mystery = A & B;
declare const m: Mystery;
m.method(""); // okay
m.method(); // okay
如果您检查 m.method
,您会看到它有两个重载:
// 1/2 method(p: any): void
// 2/2 method(): void
因此可以通过两种方式之一调用。