Type Annotations S1
中文标题:类型注解
- 阶段: Stage 1
- 状态: 进行中
- ECMAScript 版本: —
- 同步时间: 2026年8月26日
- English original · 官方仓库
该提案旨在将类型注解作为注释添加到 JavaScript,使 TypeScript 和 Flow 等外部类型检查器无需转译即可分析代码。它引入了注解、声明、泛型和导入的语法,同时明确省略了诸如枚举和 JSX 等影响运行时的特性。
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
⚠️ 本文档未定期更新。有关最新信息,请参阅 2022 年(3 月 29 日、3 月 31 日)和 2023 年(3 月 22 日、9 月 27 日)的 TC39 会议记录。
ECMAScript 提案:类型注解
该提案旨在使开发者能够向其 JavaScript 代码添加类型注解,并允许由 JavaScript 外部 的类型检查器检查这些注解。 在运行时,JavaScript 引擎会忽略它们,将类型视为注释。
该提案的目标是使开发者能够运行用 TypeScript、Flow 以及其他 JavaScript 静态类型超集编写的程序,无需任何转译,前提是它们遵循该语言中一个相当大且合理的子集。
状态
阶段: 1
作者:
- Gil Tayar
- Daniel Rosenwasser (Microsoft)
- Romulo Cintra (Igalia)
- Rob Palmer (Bloomberg)
- ...以及其他众多贡献者,参见 历史。
提案负责人:
- Daniel Rosenwasser (Microsoft)
- Romulo Cintra (Igalia)
- Rob Palmer (Bloomberg)
请在 issues 中留下您的任何反馈!
动机:解除 JavaScript 的分叉
在过去十年中,静态类型检查的案例已被相当成功地证明。 微软、谷歌和 Facebook 分别发布了 TypeScript、Closure Compiler 和 Flow。 这些工作是对 JavaScript 的大型投资,以收获他们在其他静态类型语言中看到的生产力提升,包括更早地发现错误,以及利用强大的编辑器工具。
就 TypeScript、Flow 等而言,这些 JavaScript 变体带来了在 JavaScript 中声明和使用类型的便捷语法。 这种语法大多不影响运行时语义,实际上,将这些变体转换为纯 JavaScript 的大部分工作等同于擦除类型。
对符合人体工程学的类型注解语法的强烈需求导致了具有自定义语法的 JavaScript 分叉。这引入了开发者摩擦,并意味着广泛使用的 JavaScript 分叉难以与 TC39 协调,并且必须冒语法冲突的风险。本提案为注释形式化了一个符合人体工程学的语法空间,以整合类型检查型 JavaScript 分叉的需求。
社区使用和需求
静态类型 是 State of JS 调查 每年记录中最被遗漏的语言特性:2020、2021、2022、2023 和 2024。

JavaScript 编译的趋势
JavaScript 中类型语法的兴起与降级编译(有时称为“转译”)的兴起相吻合。 随着 ES2015 被标准化,JavaScript 开发者看到了精彩的新特性,但由于支持旧浏览器的限制,他们无法立即使用这些特性。 例如,箭头函数可以提供开发者便利性,但无法在每位最终用户的机器上运行。 因此,像 Traceur、TypeScript 和 Babel 这样的项目通过将 ES2015 代码重写为可在旧运行时上工作的等效代码来填补这一空白。
由于 JavaScript 原生不支持类型语法,因此必须存在某种工具来在运行任何代码之前移除这些类型。 对于 TypeScript 和 Flow 这样的类型系统,将类型擦除步骤与语法降级步骤集成是有意义的,这样用户就不需要运行单独的工具。 最近,一些打包器甚至开始同时做这两件事。
但随着时间的推移,我们预计开发者对降级编译的需求会减少。 常青浏览器已成为常态,在后端,Node.js 和 Deno 使用非常新版本的 V8。 随着时间的推移,对于许多静态类型系统用户而言,编写代码和运行代码之间唯一必要的步骤将是擦除类型注解。
构建步骤为编写代码增加了另一层关注点。 例如,确保构建输出的新鲜度、优化构建速度以及管理用于调试的 sourcemap,这些都是 JavaScript 最初避开的关注点。 这种简单性使 JavaScript 更容易上手。
本提案将减少对构建步骤的需求,这可以使某些开发设置更加简单。 用户可以简单地运行他们编写的代码。
JSDoc 类型注解的局限性
虽然构建工具使用起来并不难,但它们对许多开发者来说是另一个进入障碍。 这部分是 TypeScript 团队投资于支持在 JSDoc 注释中表达类型的原因之一。 JSDoc 注释在 JavaScript 社区中已有一些用于记录类型的先例,并且 Closure 编译器利用了这些类型。
这种注释约定常见于构建脚本、小型 Web 应用、服务端应用以及其他添加构建工具的成本/收益权衡过高的地方。 即使没有进行类型检查诊断,编辑器在其底层的 JavaScript 编辑体验中也会利用这种注释约定。
以下是来自 TypeScript 的 JSDoc 参考 的基于 JSDoc 的类型语法示例。
这是提议的语法,与大多数类型检查器兼容。
JSDoc 注释通常更冗长。 除此之外,JSDoc 注释仅提供 TypeScript 支持的功能子集,部分原因是在 JSDoc 注释中很难提供具有表现力的语法。
尽管如此,基于 JSDoc 的语法仍然有用,并且 JavaScript 中对某种形式的类型注解的需求足够大,以至于 TypeScript 团队投资于它,社区也创建了支持使用 JSDoc 进行类型检查的工具[1], [2]。
由于这些原因,本提案探索并期望更大的语法直接出现在 JavaScript 源文件中,并被解释为注释。
提案
以下是阶段 1 提案。请这样对待它,预计会有更新,欢迎 反馈。TC39 阶段流程文档
类型注解
类型注解允许开发者显式声明变量或表达式预期是什么类型。
注解遵循声明的名称或绑定模式。
它们以 : 开头,后跟实际类型。
在上面的示例中,x 被注解为类型 string。
进行静态类型分析的工具可以利用该类型,并且可能会选择在 x = 100 语句上报错;
然而,遵循本提案的 JavaScript 引擎将无错误地执行这里的每一行。
这是因为注解不会改变程序的语义,并且等同于注释。
注解也可以放在参数上以指定它们接受的类型,并放在参数列表末尾以指定函数的返回类型。
这里我们为每个参数类型指定了 number,并为 equals 的返回类型指定了 boolean。
类型声明
很多时候,开发者需要为类型创建新名称,以便无需重复即可轻松引用,并且可以递归声明。
声明类型的一种方式——特别是对象类型——是使用 interface。
在 interface 的 { 和 } 之间声明的任何内容都会被完全忽略。
类型别名是另一种声明。 它可以为更广泛的类型集合声明一个名称。
作为类型声明的类
本提案将允许类成员(如属性声明和私有字段声明)指定类型注解。
对于大多数类型检查器,带注解的类成员将有助于构造给定类时产生的类型。
在上面的示例中,类型检查器可以假设有一个名为 Person 的新类型,具有 string 类型的属性 name 和返回 string 的方法 getGreeting;
但与本提案中的任何其他语法一样,这些注解不会影响程序的运行时行为。
类型种类
上面的示例使用了诸如 string、number 和 boolean 之类的类型名称,大多数静态类型检查器支持语法比单一标识符更复杂的类型。
下表给出了一些示例。
此表并不全面。
本提案的目标是找到一组合理的语法规则来容纳这些构造(以及更多),而不会阻止现有类型系统在该空间中进行创新。
这一挑战在于表示类型的结束——这涉及明确说明哪些标记可以或不可以是注释的一部分。
一个简单的第一步是确保括号内((...)、[...]、{...} 或 <...>)的任何内容都可以立即跳过。
再进一步就更难了。
这些规则尚未确定,但将在本提案推进时更详细地探讨。
另请参阅
参数可选性
在 JavaScript 中,参数在技术上通常是“可选的”——当省略实参时,函数的参数将在调用时被赋值为 undefined。
这可能是错误的来源,并且参数是否实际上是可选的是一个有用的信号。
为了指定参数是可选的,参数名后面可以跟一个 ?。
导入和导出类型
随着项目变大,代码被拆分为模块,有时开发者需要引用在另一个文件中声明的类型。
可以通过在它们前面加上 export 关键字来导出类型声明。
也可以使用 export type 语句导出类型。
相应地,另一个模块可以使用 import type 语句来引用这些类型。
这些指定 type 的声明也作为注释。
它们不会触发任何模块的解析或包含。
类似地,它们的命名绑定不会被验证,因此如果命名绑定引用了从未声明的实体,则不会出现运行时错误。
相反,设计时工具可以自由地静态分析这些声明并在这种情况下发出错误。
仅类型的命名绑定
为值和类型维护单独的导入语句可能很麻烦——尤其是当许多模块同时导出类型和值时。
为了支持包含类型和值绑定的 import 或 export 语句,用户可以通过在绑定前加上 type 关键字来表达哪些绑定是仅类型的。
上述内容的主要区别在于,即使所有绑定都声明为仅类型,此类导入也会被保留。
类型断言
类型系统对表达式的运行时类型没有完美的信息。 在某些情况下,需要告知它们在给定位置一个更合适的类型。 类型断言——有时称为强制转换——是断言表达式静态类型的一种手段。
在 TypeScript 中选择“类型断言”一词是为了与通常具有运行时含义的“强制转换”概念保持距离。 相比之下,类型断言没有运行时行为。
非空断言
这是类型断言的一个常见用例,类型检查器从可空类型中过滤掉 null,检查值是否既不是 null 也不是 undefined。
例如,可以编写 x!.foo 来指定 x 不能是 null 或 undefined。
在 TypeScript 中,非空断言运算符没有运行时语义,本提案将以类似方式指定它; 然而,有一个案例是添加非空断言作为运行时运算符。 如果首选运行时运算符,那很可能会成为一个独立的提案。
泛型
在现代类型系统中,仅仅谈论存在一个 Array 是不够的——通常,我们关心 Array 中有什么(例如,我们是否有一个 strings 的 Array)。
泛型为我们提供了一种讨论类型上的容器等事物的方式,而我们讨论一个 strings 的 Array 的方式是编写 Array<string>。
与本提案中的其他所有内容一样,泛型没有运行时行为,并且会被 JavaScript 运行时忽略。
泛型声明
泛型类型参数可以出现在 type 和 interface 声明上。
它们必须以标识符后的 < 开头,以 > 结尾:
函数和类也可以有类型参数,但变量和参数不能。
泛型调用
可以显式指定泛型函数调用或泛型类实例化的类型实参,在 TypeScript 中 或在 Flow 中。
上述语法已经是用户可能依赖的有效 JavaScript,因此我们不能按原样使用这种语法。
我们期望某种形式的新语法可用于解决这种歧义。
目前尚未提出具体解决方案,但一个示例选项是使用诸如 :: 之类的语法前缀
这些类型实参(::<type>)将被 JavaScript 运行时忽略。
这种无歧义的语法也合理地可以在 TypeScript 中采用。
this 参数
函数可以将名为 this 的参数作为第一个参数,并且该参数(及其类型)在运行时被忽略。
它对函数的 length 属性没有影响,也不影响 arguments 之类的值。
可以预期,在箭头函数中使用 this 参数将要么被语法禁止,要么触发早期错误。
故意省略
我们认为以下项目明确排除在本提案的范围之外。
省略:生成代码的 TypeScript 特定特性
TypeScript 中的一些构造不受本提案支持,因为它们具有运行时语义,生成 JavaScript 代码而不是简单地被剥离和忽略。 这些构造不在本提案范围内,但可以通过单独的 TC39 提案添加。
省略:JSX
JSX 是 JavaScript 的一种类似 XML 的语法扩展,旨在由预处理器转换为有效的 JavaScript。 它在 React 生态系统 中被广泛使用,但它也曾被用于不同的库和框架。 由于 JSX 直接与 JavaScript 代码交织,JavaScript 的编译器和类型检查器通常也支持检查和转换 JSX。 一些用户可能希望 ECMAScript 也能直接支持 JSX 转换,以扩展无需构建步骤即可处理的用例集。
我们不认为 JSX 在本提案的范围内,因为:
- JSX 是一个正交特性,独立于可选静态类型。本提案不影响通过独立提案将 JSX 引入 ECMAScript 的可行性。
- JSX 语法在转换时会展开为有意义的 JavaScript 代码。本提案仅涉及语法擦除。
尚待讨论
有几处语法可以很好地与“类型即注释”模型契合,但可能感觉有点越界。 我们认为本提案可以包含或不包含这些语法,并在下面提供一些替代方案。
环境声明
有时有必要告知类型检查器某些值存在,甚至模块存在。
类型系统拥有所谓的环境声明,其中声明将省略其实现。
虽然类型系统通常有自己的“声明文件”格式,仅用于此类声明(例如 TypeScript 中的 .d.ts 文件,Flow 中的 .flow.js 等),但在实现文件(即 .js 文件)中声明这些值也很方便。
在当今的类型系统中,declare 关键字可以放在诸如变量、函数和类声明之类的绑定前面。
目前,本提案没有为环境声明保留空间,但这是一个选项。
函数重载
在现有的类型系统中,函数/方法声明可以省略其主体。 这用于函数重载,它传达函数的返回类型随其输入而变化。
目前,本提案没有为重载声明保留空间,但这是一个选项。
类和字段修饰符
TypeScript 和其他类型系统中的几个关键字在类型上下文之外使用:
abstract类和方法private、protected和public的字段和方法放置readonly字段override字段和方法
作为示例,本提案可以支持以下语法:
如果允许这些作为本提案的一部分,语义将是忽略所有它们,将上述类视为与以下相同。
与类型一样,这些是不在运行时强制执行的“软保证”,但由类型检查器检查。 可能需要对语法稍作调整,以禁止在这些新的上下文关键字之后出现换行。
将这一组关键字包含在内并允许它们被“错误地”使用可能会感觉很奇怪。
此外,随着时间的推移可能会添加更多关键字(例如最近添加的 override)。
作为实现相同功能的一种方式,现有类型系统可以在类型注解语法和现有注释语法之间找到折衷方案。 例如,TypeScript 已经在 JSDoc 中支持其中一些修饰符。
可能还有另一个方向,本提案扩展注释语法仅以支持诸如这些以特定符号开头的修饰符。
上述想法试图在语法上不过度扩张与实现与现有类型系统的最大兼容性之间取得平衡。 我们对这里提出的四种解决方案中的任何一种或其他想法都持开放态度。
常见问题解答
JavaScript 是否需要静态类型检查?
鉴于组织和团队在构建和采用类型检查器上投入了大量精力,答案是肯定的。 也许并非每个开发者都会使用静态类型检查,这没关系——这就是为什么本提案使类型注解完全可选; 然而,生态系统对使用类型的需求是不可否认的。
TypeScript 很好地证明了这一点——它获得了非常广泛的使用,并且有强烈的信号表明人们希望继续使用它。 它是选择加入的,但在生态系统中占有重要地位,如今 TypeScript 支持被视为库的巨大优势。
问题不在于 JS 是否应该有类型,而是“JS 应该如何与类型配合?” 一个有效的答案是,当前的生态系统提供了足够的支持,类型可以提前单独剥离,但本提案可能比这种方法更有优势。
为什么不在 TC39 中为 JS 定义类型系统?
TC39 有编程语言设计的传统,倾向于局部的、健全的检查。 相比之下,TypeScript 的模型——它已对 JS 开发者非常成功——是围绕非局部的、尽力而为的检查。 TypeScript 风格的系统在应用程序启动时检查成本高昂,并且在每次运行 JavaScript 应用程序时都会重复。
此外,在浏览器中直接定义类型系统意味着改进的类型分析将成为 JavaScript 应用程序用户 的破坏性更改,而不是开发者。 这会违反有关 Web 兼容性的目标(即“不破坏 Web”),因此类型系统创新将变得几乎不可能。 允许其他类型系统单独分析代码为开发者提供了选择、创新和随时选择退出检查的自由。
相反,试图为 JavaScript 添加完整的类型系统将是一项巨大的多年努力,并且很可能永远不会达成共识。 本提案认识到这一事实,也认识到社区已经进化出他们已经很满意的类型系统。
这个提案与 TypeScript 有什么关系?
本提案是一种平衡行为:试图尽可能与 TypeScript 兼容,同时仍然允许其他类型系统,并且不会过多地阻碍 JavaScript 语法的演进。 我们承认完全兼容不在范围内,但我们将努力最大限度地提高兼容性并最小化差异。
本提案将需要 ECMAScript 和 TypeScript 本身的工作,其中 ECMAScript 扩展其当前语法,但 TypeScript 做出某些让步,以便类型能够适应该空间。
如其他地方所述,一些构造(如 enum 和 namespace)已被搁置,可以选择在 TC39 中单独提出。
其他构造仍在讨论中(如 class 修饰符和环境声明)。
对于其他构造,将需要努力消除当前代码的歧义(如箭头函数)。
TypeScript 将继续与 JavaScript 稍受限制的类型语法并存。 换句话说,所有现有的 TypeScript 项目将继续编译,并且无需更改任何现有的 TypeScript 代码库。 但是,采用任何存在于 JavaScript 类型语法之外的现有 TypeScript 代码将无法运行——它仍然需要被编译掉。
TypeScript 是否应该在 TC39 中标准化?
TypeScript 在语法和类型分析方面都持续快速发展,这对用户有利。 将这种演进与 TC39 绑定有可能会阻碍这种利益。 例如,TypeScript 升级通常要求用户修复类型问题,因为规则会变化,并且这通常被认为是“值得的”,因为发现了真正的错误; 然而,这种版本升级路径对 ECMAScript 标准来说是不可行的。 走向标准化将需要更加保守。 这里的目标是支持像 TypeScript 这样的系统在多样化环境中更广泛地部署,而不是阻碍 TypeScript 的演进。
TypeScript 是否应该被认可为 JS 的官方类型系统?
目前尚不清楚 JavaScript 的“官方”类型系统意味着什么。 例如,JavaScript 没有“官方”的 linter 或“官方”的格式化程序。 相反,存在一些工具,它们随着时间的推移而变得流行并不断演进。
此外,类型检查器,如 Flow、Closure 和 Hegel,可能希望使用本提案来使开发者能够使用他们的类型检查器来检查他们的代码。 使本提案仅关于 TypeScript 可能会阻碍这项工作。 在这个空间中保持友好竞争对 JavaScript 有益,因为它可以促进实验和新想法。
为什么不在各种系统中非官方地构建 TS 检查和编译?
许多系统,例如 ts-node 和 deno,都尝试过这样做。 除了启动性能问题外,一个常见的问题是跨版本和模式的兼容性,在类型检查语义、语法和转译输出方面。 本提案不会涵盖所有这些功能的需求,但它将提供一种兼容的语法和语义,以便在许多需求上统一起来。
为什么不坚持使用现有的 JS 注释语法?
尽管可以在现有 JavaScript 注释中定义类型,正如 Closure 和 TypeScript 的 JSDoc 模式所做的那样,但这种语法更冗长且不符合人体工程学。
考虑一下 JSDoc 类型注解在 TypeScript 之前就出现在 Closure Compiler 中,并且 TypeScript 的 JSDoc 支持已经存在多年。 虽然 JSDoc 中的类型在逐年增长,但大多数类型检查的 JavaScript 仍然是用 TypeScript 语法编写在 TypeScript 文件中。 Flow 的情况也类似,Flow 的类型检查器可以分析现有 JavaScript 注释中的 Flow 风格语法,但大多数 Flow 用户继续使用直接的注解/声明语法。
难道所有 JS 开发者不都转译吗?移除类型消除步骤真的会有帮助吗?
JavaScript 生态系统一直在慢慢回归无转译的未来。IE11 的退役和实现最新 JavaScript 标准的常青浏览器的兴起意味着开发者可以再次无需转译即可运行标准 JavaScript 代码。 浏览器和 Node.js 中原生 ES 模块的出现也意味着,至少在开发中,生态系统正在走向甚至不需要打包的未来。 特别是 Node.js 开发者,历史上一直回避转译,如今在无转译带来的开发便利和 TypeScript 等语言带来的开发便利之间左右为难。
实现本提案意味着我们可以将类型系统添加到“不再需要转译的事物”列表中,并使我们更接近一个转译是可选而不是必需的世界。
类型是否可以通过如 TypeScript 的 emitDecoratorMetadata 这样的运行时反射获得?
这里的提案与 Python 的类型有显著不同,因为本提案中的类型被完全忽略,而不是作为表达式求值或在运行时作为元数据访问。
本提案明确不对运行时反射采取立场,将进一步的工作留给未来的提案。主要原因在于本提案不会阻碍这个领域的进一步工作,反而会促进它。这也是 Python 在向语言中添加类型时采取的路线。
依赖装饰器元数据的用户可以继续按需利用构建步骤。
这个提案是否使所有 TypeScript 程序成为有效的 JavaScript?
TypeScript 中的大多数构造是兼容的,但不是全部, 并且大多数不兼容的构造都可以通过简单的 codemod 修改进行转换,使其既与 TypeScript 兼容又与本提案兼容。
有关更多信息,请参阅 “尚待讨论” 和 “故意省略” 部分。
这个提案是否使所有 Flow 程序成为有效的 JavaScript?
Flow 与 TypeScript 非常相似,因此大多数类型构造都可以工作,但有一个类似的警告,即某些类型可能需要用括号括起来才能与本提案兼容。
两个不符合本提案的构造是类型转换(例如 (x: number))和opaque 类型别名(例如 opaque type Meters = number)。
Flow 可以考虑修改这些语言特性以符合本提案,例如采用 as 运算符作为 (x: number) 的替代方案,以及 type Meters = (new number)。
本提案也可以为 opaque 类型别名保留空间。
那 .d.ts 文件和“libdef”文件呢?
声明文件 和 库定义 文件分别被 TypeScript 和 Flow 用作一种“头文件”,描述值、其类型以及它们存在的位置。 本提案可以安全地忽略它们,因为它不需要解释它们内部类型信息的语义。 TypeScript 和 Flow 将继续像今天一样读取和分析这些文件。
这个提案是否意味着 TypeScript 开发者必须修改他们的代码库?
不。TypeScript 可以继续是 TypeScript,不会对兼容性或代码库产生任何影响或更改。 本提案将赋予开发者选择限制自己使用 TypeScript 的特定子集的权利,该子集可以作为 JavaScript 运行而无需转译。
开发者可能仍然希望出于其他原因使用 TypeScript 语法:
- 使用 JavaScript 中不支持某些语法特性(例如
enum和参数属性) - 与现有代码库的兼容性,这些代码库可能遇到某些处理方式不同的语法边缘情况
- JavaScript 的非标准扩展/重新解释(例如实验性的传统装饰器、装饰器元数据或字段的 [[Set]] 语义)
如果开发者决定迁移现有 TypeScript 代码库以使用本提案中指定的 JavaScript 语法,本提案的目标是使修改最小化。 我们相信许多开发者会被移除构建步骤所激励,但其他人可能会决定坚持使用 TypeScript 编译并享受该语言的完整功能。
工具应该如何处理 JavaScript 类型语法?
鉴于某些 TypeScript 特性超出范围,并且标准 JavaScript 不会像 TypeScript 那样快速演进或支持其各种配置,因此许多工具仍然有优势支持更完整形式的 TypeScript,超出可能标准化为 JavaScript 的部分。
目前一些工具需要“插件”或选项才能使 TypeScript 支持工作。 本提案意味着这些工具可以默认支持类型语法,形成标准的、无版本的、始终开启的通用基础。 对 TypeScript、Flow 等的完整且规范的支持可以保留为可选的模式,基于此之上。
与 ReasonML、PureScript 以及其他编译为 JavaScript 的静态类型语言的兼容性如何?
虽然这些语言编译为 JavaScript,并且具有静态类型,但它们不是 JavaScript 的超集,因此与本提案无关。
直接部署类型化源代码的能力是否会导致应用程序臃肿?
回到代码在生产使用之前不一定需要编译的世界意味着开发者可能最终部署比必要更多的代码。 因此,远程服务的应用通过网络传输的负载更大,并且加载时需要解析更多文本。
然而,这种情况已经存在。 如今,许多用户省略构建步骤并发送大量注释和其他无关信息,例如未压缩的代码。 如果用例对性能敏感,则对目标生产的代码执行提前优化步骤仍然是最佳实践。
具体来说,请注意 TypeScript 编译器 tsc 没有内置的压缩选项。允许 TypeScript 用户在开发过程中跳过 tsc 不会本质上鼓励用户跳过压缩步骤,因为压缩 TypeScript 无论如何都需要单独的操作。
Babel 是另一个流行的 TypeScript、Flow 和 Hegel 转译器。Babel 的 preset-typescript 和 preset-flow 转译器不进行压缩。在 Babel 中,压缩需要单独的步骤(通常由打包器执行)。允许 Babel 用户省略 preset-typescript 不会鼓励用户跳过压缩。
在本提案中,类型注解被视为注释。使注释更有用且更易于使用是 JavaScript 的适当演进,即使我们期望开发者通过压缩移除注释和类型注解。
未经检查的类型是安全隐患吗?
本提案引入了明确不在运行时检查的类型注解。 这是有意的,以最小化注解的运行时成本并提供一致的心理模型,即类型不影响程序行为。 一个潜在的风险是用户可能没有意识到需要运行外部工具来查找类型错误,因此当类型相关的错误出现在他们的类型注解代码中时会感到惊讶。
对于当今的外部类型检查器用户来说,这种风险已经存在。 用户今天通过以下方式组合来缓解这种风险:
- 执行类型检查并主动显示类型错误的 IDE
- 将类型检查集成到项目开发工作流中,例如 npm 脚本、CI 系统、
tsc
在某些方面,如果类型确实在运行时检查,用户会更惊讶。
在其他语言中,通常存在最少的运行时类型检查。
例如,在 C++ 中,除了某些已知情况(如程序员请求时,例如 dynamic_cast)外,几乎没有运行时检查。
正如 TypeScript 所见,开发者很快就会了解到类型在运行时不起作用。 因此,与第一个问题“JavaScript 是否需要静态类型系统?”类似,这个问题已经部分被外部类型检查器的广泛成功所回答。
未来如何添加运行时检查的类型?
由于运行时性能开销,JavaScript 似乎不太可能采用普遍运行时检查的类型系统。 虽然可能还有改进空间,但过去的努力,如 TS* (Swamy et al) 表明,基于注解的运行时类型检查会增加不可忽略的减速。 这就是本提案支持纯静态类型的原因之一。
如果希望保持语言对以后添加运行时检查类型的开放性,除了这里提出的静态类型之外,我们可以在文法中显式保留语法以支持两者。
这个提案能否使运行时根据类型提示优化性能?
虽然可以理论上静态声明的类型驱动的运行时优化,但提案作者不知道在 JavaScript 中有成功实验能够有意义地击败动态类型驱动的 JIT 优化。
所提议的类型语法没有定义的语义,因此对运行时是不透明的。
这相当于问“运行时可以使用 /** 注释 **/ 来优化性能吗?”
答案是:几乎肯定不会——至少不会以标准方式。
因此,本提案本身并不直接为性能改进提供新的机会。
明确地,改进 JavaScript 的性能是本提案的一个非目标。
先前工作
具有可选可擦除类型语法的其他语言
当 Python 决定为语言添加渐近类型系统时,它分两步进行。 首先,在 PEP-3107 - Function Annotations 中创建了类型注解的提案,该提案在 Python 中指定了参数类型和函数返回类型 几年后,在 Python PEP-484 - Type Hints 中扩展了可以出现类型的位置。
这里的提案与 Python 的类型有显著不同,因为本提案中的类型被完全忽略,而不是作为表达式求值或在运行时作为元数据访问。这一差异主要由现有的社区先例驱动,其中 JS 类型系统往往不会为其类型使用 JS 表达式文法,因此无法将它们作为表达式求值。
Ruby 3 现在也实现了 RBS:类型定义位于代码旁边 并且不是代码的一部分。
将类型系统添加到 JavaScript 的语言
TypeScript、Flow、和 Hegel 是在标准 JavaScript 之上实现类型系统的语言。
提案仓库包含一个语法比较文档,其中有一个表格比较了这些现有系统中使用的类型语法。它不是详尽无遗的,我们欢迎更正或贡献。
通过注释向 JavaScript 添加类型系统的能力
TypeScript 和 Flow 都使开发者能够编写 JavaScript 代码并包含 JavaScript 运行时忽略的类型注解。
对于 Flow,这些是 Flow 注释类型,对于 TypeScript,这些是 JSDoc 注释。
参见作者关于他们对 TypeScript 的 JSDoc 注释的积极经验的博客文章 此处。
Closure Compiler 的类型检查完全通过 JSDoc 注释进行(文档)。Closure Compiler 团队收到了许多关于内联类型语法的请求,但在没有标准的情况下对此犹豫不决。
TC39 中的相关提案和讨论
我们知道的 JavaScript 类型最早的提案是 Waldemar Horwat 2000 年 7 月的“Types”规范。
2008 年 ECMAScript 第 4 版的提案草案 要求类型和可选类型注解;TC39 于 2008 年同意撤回 ES4 提案,于 2010 年发布 ES 3.1 作为 ES5。Auth0 有一篇关于 ECMAScript 4 传奇历史的博客文章。
TC39 之前讨论过 guards,它们形成了一个新的、更强的类型系统。
此前,Sam Goto 领导了围绕一个可选类型提案 的讨论,该提案旨在统一不同类型的语法和语义。 试图在不同类型检查器之间达成一致,并在语法和语义上定义足够的子集,意味着这种方法存在困难。 该计划的演进是可插拔类型,其灵感来自 Gilad Bracha 关于可插拔类型系统的想法。 本提案与可插拔类型提案极为相似,但更倾向于“类型即注释”的观点,并且正处于类型检查更广泛采用和类型检查生态系统更成熟的时期。
2015 年,Google 的 V8 团队试验了一项提案,以实现他们称为“Strong Mode”的新 JS 模式,旨在使用类型来提高站点性能。Google 以失败告终,取消了该实验。“最终我们不得不放弃这个。”
可选类型提案仓库包含指向其他先前关于 JavaScript 类型的讨论的链接:
- 2002 Javascript 2.0: Evolving a Language for Evolving Systems
- 2006 Type parameters, Type System and Structural Types and typing of initializers
- 2011 Dependent Types for Javascript (pdf)
- 2011 Guards and Trademarks
- 2014 TC39 Discussion on Types: adding syntax without semantics.
- 2015 ES8 gradual typing and part2 and ecmascript-types.