你说得对
{} extends object
应该是假的,因为不是每一个
{}
是一个
object
.
Primitives
喜欢
string
或
number
或
boolean
可分配给
{}
(接受任何非无效值),但不接受
对象
(拒绝基元)。那么,为什么允许这样做呢?
权威答案包含在
a comment on microsoft/TypeScript#56205
TS团队开发负责人:
允许
{}
用作
对象
这是一个有意的兼容性漏洞,因为
对象
并不总是存在的,所以人们用
{}
作为下一个最好的事情。
所以这是故意的
unsound
为了不破坏现实世界中大量代码的存在
对象
.
更多详细信息可以在
microsoft/TypeScript#60582
.如果
{}
不可转让给
对象
那么,两者都不会
{a: string}
然后是接受的东西
对象
s会突然
拒绝
大多数类型和接口,因为
也许 吧
它们很原始吗?(尽管
{a:string}
不可能是原始的。)因此,TypeScript可能需要积极分析每个接口,看看它是否与原语重叠,然后允许那些重叠的接口
不
重叠部分可分配给
对象
,所以
{length: number, a: string}
可分配给
对象
但是
{length: number}
不是吗?正如前文所述
another comment
TS团队开发负责人:
根本问题是,这里的不一致性不可能
远离的
,他们可以只是
移动
我们说这个项目是合法的
function foo(s: string) {
return s.length;
}
但是
为什么?
这合法吗?这是合法的,因为
一串
类型还包括来自全局的属性
String
类型。
同样,出于同样的原因,我们允许您编写此程序:
function foo<T extends { length: number }>(x: T) {
return x.length;
}
// Legal call
foo("hello world");
这里的建议意味着打破了这种一致性:如果某些表达式涉及对基元的属性访问,则不能再使用某些表达式块并以更高阶的方式讨论它们。访问
length
一个字符串现在是一个
根本不同
操作比访问
长度
一个数组。
这引发了更多的问题,考虑一下这样的问题
const p = "foo";
type X = (typeof p)["length"];
type GetLength<T> = T extends { length: infer U } : U ? never;
type Y = GetLength<typeof p>;
是类型
X
甚至合法?一个论点是肯定的,因为如果你写作,你会得到这样的结果
p.length
.另一种说法是否定的,因为这基本上相当于
GetLength
,现在将生产
never
相反。
我们生成或摄取属性名称的每个地方,现在都必须考虑该属性名称是否作为对象语法的一部分存在。例如,什么是
keyof string
?
这也隐含地打破了常见的“品牌”模式,如
string & { isNormalized: true }
自从
string & object
很明显
从未
.
我也会权衡一下这一点
interface
隐式扩展
对象
但不是
type X = { }
看起来非常不一致,可以说是违反直觉的?如果有的话,我会选择相反的。你可以想象让写作合法化
interface Foo extends object {
(并在你的代码库中使其习惯化),但必须插入一个
& object
进入a
type
宣言。但无论哪种方式,它都引入了“你必须知道的另一件事”,从保持语言可学习性的角度来看,这并不好。
所以问题是周围有一种奇怪的不健康
{}
和
对象
,而且,在不引入其他更奇怪、更不健全的情况下,所有提出的处理方法都没有奏效。