2022年3月,宣布了一个0阶段提案,将在JavaScript语言中添加类似TypeScript的类型注释。在TypeScript诞生之前,人们就对JavaScript静态类型的效用和工效进行了争论;一些开发者认为类型给语言增加了不必要的复杂性,而另一些人则认为类型增加了一个非常需要的安全网。今天,静态类型是最受欢迎的JavaScript功能之一,在语言中加入某种程度的类型语法会带来一些好处。
该提案
简而言之,该提案旨在将TypeScript的类型语法的一个子集添加到JavaScript语言中。类型注释将是可选的,并且在运行时对JavaScript代码没有影响。TypeScript和Flow编译器等工具可以使用这些注释来检查代码的类型。浏览器和其他运行时可以将这些类型视为注释,完全忽略它们。
该提案的范围是非常有限的。它为JavaScript描述了一种纯粹的附加类型语法;额外的语法不会影响JavaScript当前的语义。JavaScript代码不需要有类型注释,运行时也不需要处理注释(除了把它们当作注释来识别)。类型在运行时对代码没有影响。
为什么它是好的
在过去的几年里,静态类型在网络社区变得越来越流行。在过去几次年度Stack Overflow开发者调查中,TypeScript都位列最受欢迎语言的前5名。随着静态类型的流行,那些依赖外部维护的类型的包开始管理自己的类型。在TypeScript项目中运行npm安装时,经常会看到这样的信息。
npm WARN deprecated @types/next@9.0.0: This is a stub types definition.
next provides its own type definitions, so you do not need this installed.
虽然静态类型很流行,但使用它仍然是一个有点痛苦的过程。开发人员有两个选择:他们可以使用一种几乎是但不完全是JavaScript的语言,而且需要一个构建步骤(在Deno运行时之外),或者他们使用JSDoc或Flow注释来注释JavaScript。这两种选择都会给开发过程增加开销,而这种开销是类型不被更多使用的部分原因。即使开发者在自己的项目中采用了静态类型,他们也经常需要使用一些包,而这些包的类型是事后才想到的,或者是由第三方单独维护的代码。Type-as-comments提案可以帮助解决所有这些问题。
将类型植入语言将使它们更容易入门和使用。运行类型化的JavaScript不需要构建工具,这就减少了开发者需要安装和配置的东西。流行的编辑器,如VSCode,已经支持处理类型化的代码,所以不需要额外的努力来使用这些类型。
这个建议也将有助于那些构建步骤不方便或不可能的情况。虽然许多工具和工作流程可以隐含地处理TypeScript代码,但其他工具和工作流程不能。在这些情况下,想要使用类型的开发者必须预先编译TypeScript代码或使用基于注释的语法添加类型。这两种情况都不是很有吸引力;预编译会增加开发过程的复杂性,而注释类型的代码则非常冗长。考虑一下TypeScript的这个片段。
function loadData(url: string, limit: number): Promise<unknown[]> { ... }
当使用JSDoc注释打字时,它就会膨胀成这样。
/**
* @param {string} url
* @param {number} limit
* @returns {Promise<unknown[]>}
*/
function loadData(url, limit) { ... }
在许多情况下,额外的代码当然是值得的,但是让类型成为语言的一部分会明显更令人愉快。
在语言中添加一个标准的类型语法也可以刺激类型检查器的发展。Python是一个很好的例子,说明这可能会起作用。它有一个在PEP-3107和PEP-484标准中定义的标准类型语法,并且已经根据这些标准开发了几个高质量的类型检查器;其中两个比较流行的是mypy和Pyright。Mypy是用Python写的,可以通过一个插件系统进行扩展,但它的配置可能很复杂,而且速度相对较慢。Pyright是一个用JavaScript编写的较新的类型检查器,为VSCode的Python语言服务提供动力。它比mypy快得多,但缺乏mypy的可扩展性。因为这两者都是基于相同的标准类型,一个项目可以使用其中之一,甚至两者都使用(CI可能使用mypy,而开发者的编辑器使用Pyright)。
潜在的问题
俗话说,天下没有免费的午餐。给JavaScript添加类型会有一些坏处。
很明显,新的语法会增加解析器的复杂性,因为解析器必须处理新的语法元素。然而,这种影响是最小的,因为新的语法可以被忽略。
新的语法还可能使开发人员更难解析代码。TypeScript的粉丝们会说这读起来非常好。
function loadData(url: string, limit: number): Promise<unknown[]> { ... }
不太喜欢静态类型的开发者会指出,与相应的JavaScript相比,那里仍有更多需要阅读和处理的内容。
function loadData(url, limit) { ... }
需要记住的一个关键点是,在这个提议中,类型是可选的。开发者不需要在他们自己的代码中使用类型,而且在使用选择使用类型的依赖关系时也不会有兼容性问题。编辑者甚至可以为那些不想处理类型注释的开发者隐藏类型注释。
尽管最终,这个建议将允许在没有构建步骤的情况下使用类型化的代码,但仍需要一个过渡期,即仍然需要构建工具。幸运的是,大多数项目中已经使用的工具,如TypeScript编译器和Babel,可以处理这个问题。
当不再需要构建时,一些开发者可能会选择放弃代码优化,直接将类型化的代码部署到生产中,从而造成网络资源的浪费。虽然这当然是可能的,但这并不是一个新的问题;在使用基于注释的类型时,这种情况已经可能发生了(这明显更冗长)。现有的代码精简工具可以被更新以移除类型,减轻对性能的影响。
最重要的问题可能是,由于与现有的JavaScript语法冲突,可能需要对TypeScript进行修改。虽然JavaScript开发人员可以简单地忽略额外的类型语法,但TypeScript用户却不能。考虑一下提案中的这个例子:在TypeScript中,它代表一个简单的通用函数调用,但在JavaScript中,它代表一组比较。
add<number>(4, 5)
用于通用调用的JavaScript语法需要与现有的有效语法区分开来;提案中给出了一个例子:预置:: ,如。
add::<number>(4,5)
TypeScript将需要理解新的语法来处理JavaScript文件。它可能同时支持两种语法,就像现在的类型断言一样,传统的角括号语法可以在非TSX文件中使用,而较新的as 关键字可以在任何地方使用。然而,支持多种方式做同样的事情会增加TypeScript解析器的复杂性,当不同的语法混合在一起时,会导致代码混乱,所以最终采用标准化的类型语法会符合开发者的利益。
有些TypeScript语法可能永远不会进入JavaScript规范。TypeScript的枚举、命名空间和参数属性功能具有运行时语义,所以不包括在内。TSX语法在新的提案中被特别排除,因为基础JSX不是有效的JavaScript。而且,随着该提案朝着标准化的方向发展,其他的功能,如函数重载,对于定义一个函数接受的多种类型是很有用的,可能会也可能不会被标准化。这些遗漏将要求开发者使用TypeScript的一个子集来兼容JavaScript。
结论
总的来说,Type-as-comments提案看起来是JavaScript的一个净赢。虽然有一些潜在的缺点和兼容性问题,但其中最重要的是TypeScript而不是JavaScript,微软赞成这个提议。编写类型化代码而不需要构建步骤的能力,以及在构建步骤不方便的情况下使用类型化代码的能力,对于静态类型化的支持者来说将是一个巨大的胜利。在JavaScript方面,那些由于TypeScript的复杂性而通常避免使用它的开发者可能会被诱惑去尝试本地静态类型,从而将静态类型的好处带给更多的人。