为什么布尔值被认为与类实例兼容?

pax*_*977 9 typescript

我不确定为什么下面的代码片段有效......

class GroupLeader { /* snip */ };
function foo(leader: GroupLeader): void { /* snip: do stuff */ }

const isLeader = false;
const groupLeader = isLeader && new GroupLeader();

foo(groupLeader);
Run Code Online (Sandbox Code Playgroud)

在 REPL 中,我可以看到groupLeader最终是一个boolean类型,但是 TypeScript 编译器(版本 4.4.3)在调用foo(groupLeader).

为什么这有效?

操场

jca*_*alz 7

这是 TypeScript 中的预期行为。

TypeScript 的类型系统很大程度上是结构化的,而不是名义上的。因此,重要的是类型的形状结构,而不是其名称声明。因此,无论您是否在 声明中的任何地方提及,TypeScript 编译器都可以决定 typeA是 的子类型B(或者“A分配B”或“ A extends ”) 。仅当 的表观属性在 中具有兼容属性时才重要:BABBA

interface A {
    x: string;
    y: number;
}

interface B {
    x: string;
}

const a: A = { x: "", y: 0 };
const b: B = a; // okay, A extends B
Run Code Online (Sandbox Code Playgroud)

最后一行不是一个错误,就像在名义类型语言中那样。


请注意,“明显属性”意味着即使某些基元(如numberstring和 )也boolean可以被视为在结构上与对象类型兼容,因为当您对它们进行索引时,JavaScript会自动将一些基元包装在包装对象中。所以该类型{length: number}是以下类型的子类型string

interface L { length: number };
const l: L = "hello"; // okay
Run Code Online (Sandbox Code Playgroud)

因为string值有一个明显的lengthtype 属性number


在 TypeScript 中,class声明通常也会在结构上进行处理(尽管在某些情况下会发生类似于名义类型的类型,例如用于instanceof区分两个类或添加privateprotected成员时)。所以如果你有一个空类:

class GroupLeader { }
Run Code Online (Sandbox Code Playgroud)

该类在结构上与空对象类型相同{},空对象类型是没有成员的对象类型。因此,任何可以像对象一样被索引的值都将被视为可分配给该类:

function foo(leader: GroupLeader): void { /* snip: do stuff */ }

foo(true); // okay
foo(123); // okay
foo({}); // okay
foo(() => 3); // okay
foo(new Date()); // okay
foo(Symbol("oops")); // okay

foo(null); // error
foo(undefined); // error
Run Code Online (Sandbox Code Playgroud)

只能将nullundefined分配给GroupLeader,因为如果像对象一样对它们进行索引,null则 和会抛出运行时错误。undefined


这就是它发生的原因。通常,您希望防止这种行为,因此最好避免空类和空对象类型,即使在示例代码中也是如此。(有点过时的)TypeScript FAQ有很多关于这个的条目,比如为什么所有类型都可以分配给空接口?为什么这些空类的行为很奇怪?。如果您希望编译器将两种类型视为不同的,则应确保它们具有不兼容的形状。

添加任何boolean不共享的属性GroupLeader将会改变:

class GroupLeader { unsnip = 0 };

function foo(leader: GroupLeader): void { /* snip: do stuff */ }
foo(true); // error
foo(123); // error
foo({}); // error
foo(() => 3); // error
foo(new Date()); // error
foo(Symbol("oops")); // error

foo(new GroupLeader()); // okay
Run Code Online (Sandbox Code Playgroud)

当然,仍然存在传递结构兼容的东西的可能性:

foo({ unsnip: 123 }); // okay
Run Code Online (Sandbox Code Playgroud)

如果您愿意,您可以尝试阻止这种情况(private属性可以做到这一点),但阻力最小的方法是只编写只关心结构兼容性的代码。无论foo()实现是什么,它应该只关心 的结构leader不是声明

Playground 代码链接