从大型JavaScript程序的问题出发
2012年10月1日,微软公开推出TypeScript早期预览版,并提供语言说明书、编译器源代码和Visual Studio 2012插件。微软负责人S. Somasegar在发布公告中介绍了安德斯·海尔斯伯格的演示。团队面对的问题是:JavaScript已经从网页脚本扩展到浏览器应用和服务器程序,大型项目需要更明确的组件接口及更可靠的开发工具。
最初公开版本编号为0.8。它允许开发者保留原有JavaScript代码,再逐步加入类型信息;编译后的程序仍交给既有JavaScript环境运行。语言设计要兼顾旧代码、团队协作和编辑器支持,而不是要求用户先重写整个应用。
类型在编辑时检查,运行前擦除
0.8版说明书用一个很小的函数展示方法:把参数写成s: string,表示它应是字符串。传入“hello”符合要求,传入一个空对象则会得到类型错误提示。输出JavaScript时,参数上的类型标注被删除,函数仍按JavaScript规则运行。这样的检查把一部分错误提前到编写阶段。
开发者也不必给每个变量手写类型。声明var i = 0,编译器便能推断i为数值类型;返回字符串的函数也可推断出返回类型。编辑器利用这些结果列出可用成员、提示参数和定位符号。类型推断与显式标注互补,使已有项目可以局部采用新工具。
参考:[1]
按照对象的成员判断接口
TypeScript采用结构式的类型比较。0.8版说明书定义Point接口,要求对象具有数值成员x与y,再让getX函数接收Point。一个普通对象只要提供这两个成员,就能用于这个位置;只提供x则不满足要求。类可以显式声明实现接口,但匹配关系并不只依赖类与接口之间的名义声明。
这种方法贴近JavaScript临时组合对象的习惯。说明书中的jQuery例子还把函数调用形式与对象成员放在同一个接口里:美元符号既可作为函数接收查询字符串,也可以具有get方法。类型系统由此描述已有程序的多种使用方式,而不强迫程序库改成统一的类层次。
参考:[1]
声明文件与模块输出
后缀为.d.ts的声明文件描述现有JavaScript代码提供的类型,却不生成相应的运行时代码。0.8默认携带的lib.d.ts包括内建JavaScript库和文档对象模型的接口;其他程序库可以另写声明文件。团队于是能在保留程序库实现的同时,获得调用检查和自动补全。
模块机制负责组织代码及依赖。0.8编译器可以按CommonJS方式输出require调用,也能按异步模块定义(AMD)方式输出带依赖列表的define调用。若某个导入只用于类型说明,输出中可以不保留其运行时依赖。规范把类型之间的联系与实际需要加载的代码分别处理。
参考:[1]
真实项目与检查边界
Bierman、Abadi和Torgersen在2014年的论文中记录了微软的实际使用:在线编辑器Monaco约有22.5万行TypeScript代码,Xbox Music约16万行。他们也解释了一个重要取舍:为了兼容既有JavaScript写法,类型检查并非静态健全的保证;程序通过检查后,仍可能发生运行时类型错误。
奥胡斯大学的Feldthaus和Møller随后检查程序库声明文件,发现10个库的声明中有142处错误。他们举出的D3例子把threshold拼成theshold,导致自动补全推荐错误名称,正确调用反而可能被拒绝。声明文件扩大了工具能理解的范围,也带来了必须与实际程序库保持一致的维护工作。
0.9与1.0是后续阶段
2013年6月18日发布的0.9加入泛型等重要能力,并重整编译器以改善大型项目的编辑体验。泛型可以把数组元素、回调参数与返回结果的类型联系起来;这些能力属于初版之后的扩展,不宜归入2012年0.8已有的功能。
2014年4月2日,微软宣布TypeScript 1.0,提供Visual Studio、npm软件包和源代码等获取方式,并开始接受编译器及语言服务的社区拉取请求。2012年的公开预览、2013年的语言扩展与2014年的1.0发布,构成了各有具体内容的早期发展阶段。