TypeScript 允许我们使用超类型的变量(TypeScript 数组是协变的)为数组类型的变量添加别名:
const nums: number[] = [];
const things: (number | string)[] = nums;
things.push("foo");
nums[0] *= 3;
console.log(nums[0]); // `NaN` !!
Run Code Online (Sandbox Code Playgroud)
为什么?这似乎是保护我们免受运行时错误的好地方。鉴于 Java 因具有协变数组而被嘲笑,这似乎是一个有意的 TS 功能。
这是其他人在过时的 TypeScript 问题上提出的问题,但我没有看到任何答案。
正如您所指出的,数组协方差是不合理的,可能会导致运行时出错。其中的打字稿的设计非目标是
- 应用健全或“可证明正确”的类型系统。相反,在正确性和生产力之间取得平衡。
这意味着,如果某些不健全的语言功能非常有用,并且如果要求健全性会使语言使用起来非常困难或令人讨厌,那么尽管存在潜在的陷阱,它很可能会保留下来。
显然,在一个主要目的是描述 JavaScript 的语言中,试图保证健全性是“愚蠢的差事”。
我想说这里的根本问题是 TypeScript 想要支持一些非常有用的功能,不幸的是,这些功能一起玩起来很糟糕。
第一个是子类型,其中类型形成层次结构,单个值可以是多种类型。如果一个类型S是类型的子类型T,则值s类型S是还类型的值T。例如,如果您有一个 type 值string,那么您也可以将其用作 type 值string | number(因为它string是string | Xfor any的子类型X)。TypeScript 中的整个接口和类层次结构都建立在子类型的概念上。当S extends T或 时S implements T,表示它S是 的子类型T。如果没有子类型,TypeScript 将更难使用。
第二个是aliasing,您可以使用多个名称引用相同的数据,而不必复制它。JavaScript允许这样的:const a = {x: ""}; const b = a; b.x = 1;。除了原始数据类型,JavaScript 值都是引用。如果您尝试在不传递引用的情况下编写 JavaScript,那将是一种非常不同的语言。如果 TypeScript 强制要求将对象从一个命名变量传递到另一个变量,您必须复制它的所有数据,那么它会更难使用。
第三是可变性。JavaScript 中的变量和对象通常是可变的;您可以重新分配变量和对象属性。不可变语言更容易推理/更干净/更优雅,但改变事物很有用。JavaScript 不是一成不变的,因此 TypeScript 允许它。如果我有一个 value const a: {x: string} = {x: "a"};,我可以毫无错误地跟进a.x = "b";。如果 TypeScript 要求所有别名都是不可变的,那么它会更难使用。
但是将这些功能放在一起,事情可能会变糟:
let a: { x: string } = { x: "" }; // subtype
let b: { x: string | number }; // supertype
b = a; // aliasing
b.x = 1; // mutation
a.x.toUpperCase(); // explosion
Run Code Online (Sandbox Code Playgroud)
一些语言通过要求差异标记来解决这个问题。Java 的通配符用于此目的,但正确使用它们相当复杂,并且(据传闻)被认为是烦人和困难的。
尽管有相反的建议, TypeScript 已决定在此处不做任何事情并将所有属性类型视为协变。在这方面,生产力高于正确性。
出于类似的原因,函数和方法参数被双变量检查,直到 TypeScript 2.6 引入--strictFunctionTypes编译器选项,此时只有方法参数仍然总是双变量检查。
双变量类型检查是不健全的。但它很有用,因为它允许突变、别名和子类型化(不会因为要求开发人员跳过障碍而损害生产力)。 方法参数双方差导致 TypeScript 中的数组协方差。
好的,希望有帮助;祝你好运!
| 归档时间: |
|
| 查看次数: |
577 次 |
| 最近记录: |