这两天学习 ECMA 中突然发现 TC39 的 Stag1 提案中多了一个 2400+star 的新项目 Type Annotations,而且在 4 月 5 日再次修改了语法说明。
既然改动频率这么高,又是一个明星项目,那就一起来看看这个提案又是做什么的。
Type Annotations 提案介绍
它的设计思路大致源于 2020 和 2021 年度对 JS状态的调查。
有关静态类型的需求已经连续两个年度稳居排行榜高位了,Type Annotations 就是一个新的不会影响运行时语义的 ECMA 标准。注意这是他和 TS、flow 的主要不同。
这里插一嘴,有关排行第四的 Pipe Operator 目前都已经到了 Stage3 了,所以可以看出 JS 对类型检测的争议真的是蛮大的。
有关 Type Annotations 的实现方法,我们可以直接看他的文档:
它能直接把和 TypeScript 语法基本一致的类型说明放入 JS 代码中,并且可以通过 JS 引擎的编译执行检测。
Type Annotations 的语法和 TypeScript 非常相似,目前可以理解为除了枚举等真正有语义功能的 TS 类型,基本完全一致。
类型注释想要解决什么问题?
Type Annotations (以前也叫作 Types as Comments,前两天刚改的名字 )是一个允许在 JS 代码中包含类型的提议。我们考虑以下 TypeScript:
const testNumber: number = 1;
这段内容如果直接放在 JS 文件中一定会导致线上报错,因为目前 JS 引擎还无法直接识别 TS 语法,我们这里说 TS 语法本身分成两个部分:
- TSC: 将 TypeScript 转换为 JavaScript 和声明文件的过程。
- TSServer:为开发者在编译器中提供提示和重构的静态分析器。
开发者能够将类型注释添加到他们的 JavaScript 代码中,允许这些注释由 JavaScript 外部的一些类型检查器(TypeScript、Flow)检查。在运行时 JavaScript 引擎会忽略它们,将类型视为注释。
这样开发者就可以从他们的应用程序中删除构建步骤,使 TypeScript 和 Flow 代码库与 JavaScript 保持一致。
该提案的目的是使开发人员能够运行其他 JS 静态类型的超集来编写程序,而无需任何转译。
类型注释该如何工作?
🤔 这里首先你要意识到一点,有关类型的讨论在 ECMA 中基本每年都有提案,目前来讲他们更加倾向于与 TypeScript 类似的设计 —— 运行时可以完全忽略的语法。
Type Annotations 的想法是为 JS 引擎创造一个可以再编译和运行时完全当成注释忽略掉的语法, 不过 TypeScript、Flow 和其他工具可以使用这些类型。这样我们就可以使用 TypeScript 的类型检查,同时又能消除开发中 TS 转译成 JS 的构建步骤。
与此同时,编写代码和类型检查将保持不变。开发人员可以在支持 TypeScript 的编辑器中获得即时类型的检查和反馈,在命令行上运行 TypeScript,并将 TypeScript 添加为他们的 CI 任务的一部分。
所以这里最大的不同在于不需要构建步骤,这将大大降低 JS 开发人员对类型处理的门槛。
类型注释的原理
它的原理也很简单,我们主要是需要了解他是如何修改 ECMA 标准的。
在之前,JavaScript 引擎知道斜线和星号 (/*
) 表示多行注释的开始。
对于 JS 引擎来说,这大致意味着从代码中的 /*
开始,继续读取字符并忽略它们,直到你找到一个紧跟在星号后面的斜线 */
。
const message = "Hello, types"
/* Echo the message */
console.log(message)
// Hello, types
基于此,Type Annotations 也可以实现这样一种类似的定义:
如果 JS 引擎扫描到一个 message
变量,并且这个变量后面紧跟着一个冒号 :
,则 JS 引擎就会将冒号 :
和空格 ' '
之后的某些代码段 :string
也视为注释。
在这种情况下,一旦引擎遇到 =
字符,它将停止将代码解释为注释并读取 message
的值。
const message: string = "Hello, types"
/* Echo the message */
console.log(message)
那么以上代码在 JS 运行时,就会被视为如下的状况:
const message = "Hello, types"
/* Echo the message */
console.log(message)
当然他具体的实现原理不会这么简单,如果你有兴趣可以参考 ECMA 标准中的具体逻辑判断。
为啥说新瓶装旧酒?
TypeScript 团队之前是支持在 JSDoc 注释中表达类型的。有关 JSDoc,你可以直接参考 TS 中 JSDoc 的使用说明。我估计相当一部分人也都被迫或者自愿的使用过如下形式的代码注释。
/**
* @param a {number}
* @param b {number}
*/ function add ( a , b ) { return a + b ; }
TS 2.3以后的版本支持使用 --checkJ
对 JS 文件进行类型检查并提示错误。
你可以通过直接添加 --checkJs
或者在想要检查的 JS 文件中添加 @ts-check
来检查文件,例如下面的形式:
// @ts-check
/** @type {number} */
var x;
x = 0; // OK
x = false; // Not OK
因为这些只是文档注释,所以它们根本不会改变你的代码运行方式,但是 TypeScript 可以使用它们来为你提供更好的 JavaScript 编辑体验。
这个特性让你可以非常方便地获取一些无需构建步骤的 TypeScript 体验,您可以将其用于小型脚本、基本网页、Node.js 中的服务器代码等。
不过,这有点冗长——正常人可能更喜欢向 TS 那样直接写在函数或者声明内部,注释就是专门用来做函数注解。
也可以说 Type Annotations 就是一个优化版本的 JSDoc,你可以理解他把注释完全放入了 JS 函数内部。
function add(a: number, b: number) {
return a + b;
}
换句话说,你也可以完全认为 Type Annotations 就是 JS 支持 Flow/TypeScript/Hegel 中某些语法的说明。
这里虽然 Type Annotations 自己也说了一些 JSDoc 的案例和自己的特点,不过每个人都有自己的评价,我觉得基本就是一样的嘛==。
JSDoc comments are typically more verbose. On top of this, JSDoc comments only provide a subset of the feature set supported in TypeScript, in part because it's difficult to provide expressive syntax within JSDoc comments. Nevertheless, the JSDoc-based syntax remains useful, and the need for some form of type annotations in JavaScript was significant enough for the TypeScript team to invest in it. For these reasons, the goal of this proposal is to allow a very large subset of TypeScript syntax to appear as-is in JavaScript source files, interpreted as comments.
Type Annotations 目前的使用方式
首先要明确以下两点 Type Annotations 八成永远都不会支持。
不支持 TS 语法中的某些功能
Type Annotations 不支持 TypeScript 中具有运行时语义的某些构造,因为这些结构不能被简单的忽略掉就生成最后的 JS 代码,比如枚举类型,命名空间。使用其中之一对 TS 来说都需要转译以创建特殊的 JavaScript 来表示自定义 TypeScript 实现的功能。
不支持 JSX
JSX 是 JavaScript 的一种类似 XML 的语法扩展,我们需要通过预处理器将其转换为有效的 JavaScript。
React 目前会通过 babel 将 JSX 转译为基础 jS 语法,因为 JSX 直接与 JavaScript 代码交错,所以 JavaScript 的编译器和类型检查器通常也支持检查和转换 JSX。一些用户可能希望 ECMAScript 也可以直接支持 JSX 转换,以扩展无需构建步骤即可处理的用例集。
我们认为 JSX 不在此提案的范围内,因为:
- JSX 是一个独立于可选静态类型的特性。
- JSX 语法在转换后会扩展为有意义的 JavaScript 代码。本提议仅涉及语法擦除。
然后我们可以具体了解一下 现在支持的类型。
支持的类型种类
Name | Example | |
---|---|---|
Types References with Type Arguments | Set | |
Object Types | { name: string, age: number } | |
Array Type Shorthand | number[] | |
Callable Type Shorthand | (x: string) => string | |
Constructable Type Shorthand | new (b: Bread) => Duck | |
Tuple Types | [number, number] | |
Union Types | string | number |
Intersection Types | Named & Dog | |
this Types | this | |
Indexed Access Types | T[K] |
这张表并不全面。这个提议的目标是找到一套合理的句法规则来适应这些类型构造,所以 (...)<...> {...},都是他的覆盖范围,他目前的提案还没有完全解决这里支持的类型。如果你感兴趣,可以参考 Type Annotations 最新支持情况。
新瓶装旧酒还是... JS 的静态类型声明提案