版本 1.6
2015年8月
该规范基于开放网络基金会最终规范协议1.0版本("OWF 1.0")提供。OWF 1.0可见http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0.
TypeScript是微软公司的注册商标。
基于JavaScript开发的项目,如网络邮箱,地图,文档编辑,协作工具等在每天从事的电脑工作中,正在变成越来越重要的组成部分。 我们设计TypeScript的目的就是满足JavaScript编程团队的构建需求,以及维护大规模的JavaScript程序。 TypeScript帮助编程团队在软件组件之间定义接口以及洞悉当前JavaScript库的行为。 TypeScript可以使团队在动态加载模块的场景下减少命名冲突。 TypeScript可选的类型系统使得JavaScript编程人员可以使用高度产品化的开发工具和实践:静态检查,基于标识符的导航,语句补全以及代码重构。
TypeScript是JavaScript的语法糖。TypeScript的语法是ECMAScript6(ES6)语法的超集。 任何的JavaScript程序同样是TypeScript的程序。 TypeScript编译器的行为仅仅基于局部文档做转化,而不会更改在TypeScript中定义变量的顺序。 这使得生成的JavaScript代码可以很好的匹配原始的TypeScript文件。 TypeScript不会更改变量的名字,使得可以直接基于生成的JavaScript代码做调试。 TypeScript提供source maps功能,使得可以在源码级进行调试。 TypeScript工具可以在保存源码之后同时生成JavaScript代码,保留常规开发中测试,编辑,刷新的循环过程。
TypeScript语法包含所有ECMAScript6(ES6)的特性,包括类和模块,提供将这些特性翻译到适配ECMAScript3或者5代码的能力。
类(class)使开发者能够基于标准,表达常用的面向对象模式,增加继承等特性的可读性以及提高协作开发的能力。 模块可以使得开发者使用组件来组织代码,并且规避命名冲突的风险。 TypeScript编译器提供了模块代码生成的选项用来支持模块内容的动态或者静态的加载。
TypeScript同样提供给开发者一个可选的类型注解系统。 这些类型注解与闭包系统中的JSDoc注释类似,但是在TypeScript中,直接整合进了语言语法。 这个整合使得代码的可读性更强并且减少了类型注解与关联变量同步的花销。
TypeScript类型系统使开发者可以描述JavaScript对象功能的限制,并且可以通过工具来实施这些限制。 精简工具需要的注解的数量变得非常有用,TypeScript类型系统广泛使用了类型推断。 如下例所示,TypeScript会推论变量i的类型为number。
var i = 0;
TypeScript基于下例中的函数定义做推断,得到函数f的返回类型为string.
function f() {
return "hello";
}
得益于推断行为,开发者可以使用TypeScript语言服务。 例如,一个代码编辑器可以结合TypeScript语言服务并通过该服务在下面的截屏中找到一个字符串对象的成员。
/
在这个例子中,在没有类型注解的情况下,开发者得益于类型推断。一些有帮助性的工具,仍然需要开发者提供类型注解。 如下所示,在TypeScript中,我们可以描述一个参数的类型。
function f(s: string) {
return s;
}
f({}); //Error
f("hello"); //Ok
出现在's'参数右边的类型注解使得TypeScript类型检查器获知程序期望参数's'的类型为'string'。 在函数'f'的函数体中,工具可以假定's'的类型为'string',并且对操作过程进行类型检查,判断是否和假设的情况一致。 在第一个函数调用中,工具会抛出错误,因为'f'需要的参数为'string',而不是一个对象,如同参数类型说明。 对于该函数'f',TypeScript编译器会生成如下的JavaScript代码片段:
function f(s) {
return s;
}
在JavaScript的输出中,所有的类型注解都被擦除了。通常来讲,TypeScript会在生成JavaScript代码之前擦除掉所有的类型信息。
环境声明将在TypeScript作用域内引入一个变量,但是对于发布的JavaScript程序没有丝毫影响。 程序员可以使用环境声明告诉TypeScript编译器,另外一些组件会提供一个变量。 例如,在默认情况下,使用了未声明的变量,TypeScript编译器会打印错误。 开发者可以使用环境声明的方式,增加一些浏览器提供的通用变量。 下面的例子声明了浏览器提供的'document'对象。 因为声明中没有指定类型,编译器推测为'any'类型。 'any'类型指的是,工具对于document对象的特征和行为不做任何假设。 下面的一些例子可以举例说明开发者使用类型来进一步描述对象的预期行为。
declare var document;
document.title = "hello";
在'document'的情况下,TypeScript编译器可以自动的提供声明,因为TypeScript默认会加载一个环境声明文件'lib.d.ts'。 其中提供了内置JavaScript库以及Document Object Model的接口声明。
TypeScript编译器不能默认包含jQuery的接口,所以,如果要使用jQuery,需要提供一个如下的声明:
declare var $;
函数表达式是JavaScript的一个强大特性。 使得函数定义能够生成闭包:函数可以维持在函数定义时,上下文词法作用域中的信息。 闭包是目前JavaScript执行数据封装的唯一途径。 通过维持和使用环境中的变量,闭包可以保持信息不被闭包外的环境访问。 开发者通常使用闭包应用在如DOM的事件处理和其他异步的回调函数中,执行时,通过句柄函数进行调用。
TypeScript函数类型使得开发者在描述期望的函数签名成为可能。 一个函数签名是一个参数类型的序列加上返回类型。 下面的例子使用了函数类型描述了一个异步投票系统需要的回调函数签名。
function vote(candidate: string, callback: (result: string) => any) {
//...
}
vote("BigPig", function(result: string) {
if(result === "Bigpig") {
//...
}
});
在这个例子中,第二个参数拥有函数类型:
(result: string) => any
这个意味着第二个参数是一个返回类型为'any',拥有一个单一的'string'类型,名字为'result'的参数。
3.9.2章节关于函数类型提供了更多的信息。
开发者使用对象类型声明对对象行为的预期。 下面的代码在对象字面量中使用了对象类型指定'MakePoint'函数的返回类型。
var MakePoint: () => {
x: number;
y: number
};
开发者可以给对象类型命名,即我们可以使用接口(interface)来命名对象类型。 例如:下面的代码中,'Friend'接口声明了一个必须的属性(name)和一个可选的属性(favoriteColor)。
interface Friend {
name: string;
favoriteColor?: string;
}
function add(friend: Friend) {
var name = friend.name;
}
add({name: "Fred"}); // OK
add({favoriteColor: "blue"}); // Error, name required
add({name: "Jill", favoriteColor: "green"}); // OK
Typescript的对象类型可以规范JavaScript对象行为的多样性。 例如,jQuery定义了一个对象,'$',该对象拥有行为,如'get'(发送一个Ajax数据); 该对象还拥有属性,如'browser'(给出浏览器厂商的信息)。 然而,在具体的使用场景中,jQuery同样可以作为一个函数,可以被调用。 这些行为依赖提供给函数的参数类型。
下面的代码片段是jQuery行为的子集,足以将jQuery的使用进行简化。
interface JQuery {
text(content: string);
}
interface JQueryStatic {
get(url: string, callback: (data: string) => any);
(query: string): JQuery;
}
declare var $: JQueryStatic;
$.get("http://mysite.org/divContent", function(data: string) {
$("div").text(data);
});
'JQueryStatic'接口参考另一个接口'JQuery'。 该接口代表了由一个或多个DOM元素组成的集合的对象类型。 JQuery可以在这样的集合上集成多个操作,但是在这个例子中JQuery的使用者只需要知道可以通过给'text'方法传递一个字符串的方式, 就可以给每个在集合中的jQuery元素设置文本内容。 'JQueryStatic'接口同样包含了'get'方法,该方法可以使用提供的url参数执行一个Ajax的get操作, 当收到服务端响应时触发传递进来的callback方法。
最终,'JQueryStatic'接口包含一个赤裸的函数签名。
(query: string): JQuery;
函数签名表明该接口是可以被调用的。 这个例子阐述了一个道理,TypeScript的函数类型是一种特殊的对象类型。 具体点说就是,函数类型是一种只包含一个或多个调用签名(译者注:重载函数)的对象类型。 因此,我们可以将任何的函数类型当做对象类型的字面量来写。 下面的例子同时使用了两种方式来描述同样的类型。
var f: {
(): string;
};
var sameType: () => string = f; // OK
var nope: () => name = sameType; // Error: type mismatch (类型不匹配)
我们在上文中提到'$'函数的行为根据参数的类型有所不同。 目前为止,我们使用jQuery类型只包含了其中的一种行为:传递给'$'函数一个字符串,返回一个'JQuery'类型的对象。 为了指定多种不同的行为,TypeScript支持对函数签名的重载(overloading)。 例如,我们可以在'JQueryStatic'接口中增加一个调用签名。
(ready: () => any): any;
该签名表示'$'函数可以接受一个函数作为参数。 当传参时,参数类型为函数,jQuery将在文档可用时(dom ready)时调用该函数。 因为TypeScript支持重载,工具可以将所有文档中提示可用的方法展示出来。 并且可以在符合特定签名的方法被调用时得到正确的提示。
一般来说,在使用中一个第三方库时,不需要开发者自己添加任何附加类型。 可以使用社区提供的类型来发现(通过文档提示语句)和证实(通过静态类型检查)库的正确用法。
第3.3节对于对象类型提供了额外的信息。
(译者又憋不住了:TypeScript为开发者提供了一种方式来描述对象类型,函数参数类型以及函数返回值类型。 可以说,对象类型描述赋予js的能力主要有两个:抽象能力和限制能力。)
对象类型在结构上是可比较的。 例如,在下面的代码片段里,'CPoint'类与'Point'类是匹配的, 因为'CPoint'有'Point'所有的成员属性。 一个类可以显式的声明继承了一个接口,这样,编译器就通过对结构的对比检查该声明是否合法。 这个例子同样阐述了当一个对象字面量提供了所有必须的成员属性,经过推测,对象类型也可以匹配该对象。
interface Point {
x: number;
y: number;
}
function getX(p: Point) {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
getX(new CPoint(0, 0));
getX({x: 0, y: 0, color: "red"});
getX({x: 0});
第3.11节对于类型比较提供了额外的信息。
一般来说,TypeScript依照从底向上的方式进行类型推导: 从表达式树的叶子节点开始直到根部。 下面的例子中,通过自底向上方法在返回表达式的类型信息中,推导函数'mul'的返回类型为'number'。
function mul(a: number, b: number) {
return a * b;
}
当变量或者参数没有类型描述符或者默认值时,类型将被推导为'any', 确保编译器不需要函数调用时的非本地信息来推断函数的返回值类型。 通常来讲,这种自底向上的方法能够让开发者对于数据信息流有着清晰的直觉。
然而,在一些受限制的情况下,表达式的类型推导时使用在上下文中自顶向下的方式。 当这种情况发生时,被称作“上下文类型”。 当开发者使用了一个无法知道所有细节的类型时。上下文类型可以给编译器提供正确的信息。 例如,在一个关于jQuery的例子中,一个函数表达式被作为第二个参数传递给'get'方法。 在对表达式的类型做推导时,工具假设函数表达式的类型在'get'的函数签名中已经被定义过了, 工具可以提供一个包含了参数定义和类型的模板来做表达式类型的推导。
$.get("", function(data) {
$("div").text(data); //推导该实参的类型为string
});
上下文类型在编写对象字面量时同样有用。 当开发者已经定义了数据字面量的类型,上下文类型可以帮助工具提供对对象的成员属性提供自动补全功能。
第4.23节对于上下文类型表达式提供了额外的信息。
(译者注:这里可以通过简单的方法记忆这个概念。已经定义过的形式化结构,属于上下文类型,使用自顶向下的方式推导。 如接口,参数中的函数等。 而在表达式中确定型的变量或者结构,不属于上下文类型,使用自底向上的方式推导,如算术表达式,逻辑表达式等。)
JavaScript实践中经常会用到两个常见的设计模式:模块模式和类模式。 通常来说,模块模式使用闭包来隐藏变量和封装私有对象。 类模式使用原型链实现面向对象继承机制中的各种形式。 'prototype.js'库就是典型的例子。 TypeScript的命名空间是模块模式的一种形式。 (一个坏消息是,在ECMAScript6中,对模块模式的支持的形式与模块模式定义的不同。 基于此,TypeScript使用“命名空间”的形式使用模块模式。)
本节以及下节的命名空间章节将展示用到了类和命名空间的场景下,TypeScript生成兼容ECMAScript3和5的JavaScript代码具备一致性,符合语言习惯等特点。 TypeScript翻译的目标在于同不用工具手写出来的继承类或者命名空间的代码具备一样的正确性。 本节同样将描述在类声明中如何进行类型推导。 我们从以下的一个简单的类'BankAccout'开始。
class BankAccount {
balance: 0;
deposit(credit: number) {
this.balance += credit;
return this.balance;
}
}
下面的代码是通过TypeScript编译后生成的代码。
var BankAccount = (function () {
function BankAccount() {
this.balance = 0;
}
BankAccount.prototype.deposit = function(credit) {
this.balance += credit;
return this.balance;
};
return BankAccount;
})();
该类的声明创建了一个名为‘BankAccount’的变量,该变量的值是'BankAccount'实例的构造函数。 该声明同样创建了一个同样名称的实例类型。 如果我们将该实例类型通过接口的方式写出来,将会是下面的样子。
interface BankAccount {
balance: number;
deposit(credit: number): number;
}
如果我们构造函数的变量用函数类型的形式写出来,将会是下面的样子。
var BankAccount: new() => BankAccount;
该函数签名使用了关键字'new'作为前缀,标识'BankAccount'函数会作为构造函数被调用。 对于函数类型来说,可以同时拥有被调用和作为构造函数两种签名。 例如,JavaScript的内置对象Date,同样包含了上述两种行为。
如果我们想要银行账户有一个初始的余额,可以通过构造函数来增加。
class BankAccount {
balance: number;
constructor(initially: number) {
this.balance = initially;
}
deposit(credit: number) {
this.balance += credit;
return this.balance;
}
}
这版本的'BankAccount'类需要我们传一个参数进去,赋值给'balance'属性。 为了简化这种常用的写法,TypeScript接受如下的短语法。
class BankAccount {
constructor(public balance: number) {
}
deposit(credit: number) {
this.balance += credit;
return this.balance;
}
}
'public'关键字表示构造函数中的参数将被保留作为一个成员属性。 Public是类成员的默认访问属性,但一个开发者可以同样将成员的访问属性设置为Private或者Protected。 可访问性是设计阶段的构想,在静态类型检查中执行,但是不会影响运行时代码的执行。
TypeScript类同样支持继承,如下面的例子所示。
class CheckingAccount extends BankAccount {
constructor(balance: number) {
super(balance);
}
writeCheck(debit: number) {
this.balance -= debit;
}
}
在这个例子中,'CheckingAccount'类派生自'BankAccount'类。 'CheckingAccount'类的构造函数使用'super'关键字调用了'BankAccount'的构造函数。 在生成的JavaScript代码中,'CheckingAccount'的原型将会链入'BankAccount'的原型链中。
TypeScript的类同样可以指定静态成员。 静态类成员变成类构造函数的一个属性。
第8节对于类将提供更多的信息。
TypeScript允许开发者使用枚举类型将数字常量整理成集合的形式。 例如,在一个计算器的应用程序中,创建一个枚举类型来表示操作类型。
const enum Operator {
ADD,
DIV,
MUL,
SUB
}
function compute(op: Operator, a: number, b: number) {
console.log("the operator is" + Operator[op]);
}
在这个例子中,compute函数使用枚举类型将操作'op'做了记录:从枚举值'op'反向映射到与该字符串对应的枚举成员的值。 例如,'Operator'的声明自动从0开始,对属性赋值依次赋值。 第9节描述了可以明确地将整数赋值给枚举成员,同样可以通过字符串值来找到枚举成员。
当枚举类型声明时附加了修饰符'const',编译器将生成一个枚举成员,该成员被赋予的值是一个JavaScript常量(被注释的部分)。 这可以在很多JavaScript引擎中提升性能。
例如,'compute'函数将包含一个如下的switch语句。
switch(op) {
case Operator.ADD:
//execute add
break;
case Operator.DIV:
//execute div
break;
// ...
}
如该switch语句,编译器将生成如下代码。
switch (op) {
case 0 /* Operator.ADD */:
//execute add
break;
case 1 /* Operator.DIV */:
//execute div
break;
// ...
}
JavaScript 的实现上可以使用这些明确地常量针对switch语句生成高效的代码。 例如,可以构建一个索引了case值的跳表(jump table)。