子类型与其超类型的交集是否与子类型相同?



给定以下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 & BB是相同的。编译器甚至会认为A & BB是可相互赋值的:

function foo<A, B extends A>(u: B, m: A & B) {
u = m; // okay
m = u; // okay
}

虽然不完全相同:

function foo<A, B extends A>(u: B, m: A & B) {    
var v: B;
var v: A & B; // error, not considered identical
}

在实践中,会有编译器将A & BB区别对待的情况。其中一些是编译器实现细节,被视为错误或设计限制;我得把这些都挖出来。

但是A & BB可能很容易不同的一个特定地方与如何将调用签名的交叉点解释为可以以多种方式调用的重载函数有关,而具有调用签名的接口的扩展往往只是覆盖父接口调用签名并且只能以一种方式调用。例如:

interface A { method(p: any): void }
interface B extends A { method(): void }

这是允许的,因为形参较少的函数可以赋值给形参较多的函数。类型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

,因此可以用两种方法中的任何一种调用。


Playground链接到代码

最新更新