我不确定为什么下面的代码片段有效...class GroupLeader { /* snip */};function foo(leader: GroupLeader): void { /* snip: do stuff */}const isLeader = false;const groupLeader =
我不确定为什么下面的代码片段有效......
class GroupLeader { /* snip */ };
function foo(leader: GroupLeader): void { /* snip: do stuff */ }
const isLeader = false;
const groupLeader = isLeader && new GroupLeader();
foo(groupLeader);
在 REPL 中,我可以看到 groupLeader
最终是一个 boolean
类型,但是调用时 TypeScript 编译器(版本 4.4.3)没有生成任何错误 foo(groupLeader)
.
为什么这会起作用?
操场
这是 TypeScript 中的预期行为。
TypeScript 的类型系统在很大程度上是 结构性的 而不是 名义上的 。因此, 重要的是类型的 or 或 结构 名称 or 声明 ,无论您是否 A
在声明中的任何地方 ,TypeScript 编译器都可以决定类型 是 B
的 A
子类型 (或“ 可 B
分配 A
给 B
”或“ A
扩展 B
在中具有兼容属性 B
时才重要 A
:
interface A {
x: string;
y: number;
}
interface B {
x: string;
}
const a: A = { x: "", y: 0 };
const b: B = a; // okay, A extends B
最后一行不像名义类型语言那样是一个错误。
请注意,“明显属性”意味着即使 像 、 和 这样 number
, string
基元 boolean
可以被视为在结构上与对象类型兼容,因为当您索引它们时,JavaScript 会自动将一些基元包装在包装器对象中 。因此类型 {length: number}
是 的子类型 string
:
interface L { length: number };
const l: L = "hello"; // okay
因为 string
值具有 length
类型的明显属性 number
.
在 TypeScript 中, class
声明 通常也按结构处理 (尽管在某些情况下会出现名义上的类型,例如 using instanceof
to distinguish between two classes 添加 private 或 protected 成员 add private
or protected
members )。因此,如果你有一个空类:
class GroupLeader { }
此类在结构上与空对象类型(没有成员的对象类型)相同 {}
。因此, 任何 可以像对象一样索引的值都将被视为可分配给该类:
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
只有 null
和 undefined
不能分配给 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
当然,仍然存在传递一些结构兼容的内容的可能性:
foo({ unsnip: 123 }); // okay
如果需要,您可以尝试阻止这种情况( private
属性可以做到这一点),但阻力最小的路径是只编写只关心结构兼容性的代码。无论 的 foo()
实现是什么,它都应该只关心 的 结构 leader
而不是 声明 .
游乐场链接到代码