微软在2010年第一次开始研究TypeScript时,大家对JavaScript的普遍态度是:这是一种有问题的语言,需要被修正。对于框架和源到源编译器来说,为JavaScript添加缺失的特性,如类、装饰器和模块系统,是很常见的。TypeScript也不例外,它早起版本也包括了类、枚举和模块的自制版本。
TC39–管理JavaScript的标准机构,在核心JavaScript语言中添加了许多同样的特性。而这些添加的特性与TypeScript中存在的特性不兼容。“是采用标准中的新特性还是破坏现有的代码?”,TypeScript选择了后者,并最终阐述了它目前的管理原则:TC39定义了运行时,而TypeScript只在类型空间上进行创新。
而在做出这个决定之前,它还保留了一些特性。认识和理解这些特性很重要,因为它们不符合语言其他部分的模式。一般来说,我们建议避免使用它们,以是两者关系尽可能保持清晰。
理解TypeScript和JavaScript的关系:
- TypeScript 是 JavaScript 的超集。换句话说,所有 JavaScript 程序已经是 TypeScript 程序了。TypeScript具有一些自己的语法,因此 TypeScript 程序通常不是合法的 JavaScript 程序。
- TypeScript 添加了一个类型系统,该类型系统可对 JavaScript 的运行时行为进行建模,并尝试发现将在运行时引发异常的代码。但不要期望它会标记所有异常。通过类型检查器的代码仍可能在运行时抛出异常。
- 尽管 TypeScript 的类型系统在很大程度上对 JavaScript 的运行时行为进行了建模,但仍有一些 JavaScript 允许的行为,TypeScript 选择禁止,如使用错误数量的参数调用函数。
参数属性
当初始化一个类时,通常会将属性分配给一个构造函数参数:
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
// TypeScript提供了一个更紧凑的语法
class Person {
constructor(public name: string) {}
}
这被称为“参数属性”,上面两者代码等价。但参数属性有几个需要注意的问题:
- 它们是少数几个在编译成JavaScript时会生成代码的结构之一。一般来说,编译只涉及擦除类型(而非增添代码)
- 因为参数只在生成的代码中使用,所以源代码看似有未使用的参数
- 参数和非参数属性的混用会隐藏你的类的设计
例如:
class Person {
first: string;
last: string;
consctructor(public name: string) {
[this.first, this.last] = name.split(' ');
}
}
这个类有三个属性(first, last, name),但这很难从代码中读明白,因为只有两个属性列在构造函数之前。如果构造函数也接收其他参数,情况会更遭。
如果你的类只有参数属性组成,而没有方法,你可以考虑把它做成interface,并使用对象字面量。请记住,对由于结构类型,两者是可以相互赋值的:
class Person {
constructor(public name: string) {}
}
const p: Person = {name: 'Bob'}; // OK
关于参数属性,大家意见不一。有人很欣赏它们能够节省按键。但,它们与TypeScript其余部分的模式不相符,事实上这可能会让其他开发者难以摸清这种模式。尽量避免通过混用参数和非参数属性来隐藏你的类的设计。
命名空间和三斜线导入
在ECMAScript 2015之前,JavaScript并没有一个官方的模块系统,而是在不同的环境中以不同的方式加上了这个确实的特性。Node.js使用了require和module.exports,而AMD使用了一个带有回调的define函数。
TypeScript也用自己的模块系统填补了这个空白,它通过module关键字和“三斜线”导入来完成。在ECMAScript 2015增加了官方模块系统后,TypeScript增加了namespace作为module的同义词,以避免混淆。
namespace foo {
function bar() {}
}
/// <reference path="other.ts">
foo.bar();
在类型声明之外,三斜线导入和module关键字只是一个历史遗迹。而在你的代码中应该使用ECMAScript 2015风格的模块(import 和 export)。
装饰器
装饰器可以用来标注和修改类、方法和属性。例如,你可以定义一个logged注解,来对类上的某个方法的所有调用打印日志:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@logged
greet() {
return `Hello, ${this.greeting}`;
}
}
function logged(target: any, name: string, descriptor: PropertyDescriptor) {
const fn = target[name];
descriptor.value = function() {
console.log(`Calling ${name}`);
return fn.apply(this, arguments);
};
}
console.log(new Greeter('Dave').greet());
// Calling greet
// Hello, Dave
这个特性最初是为了支持Angular框架而添加的,需要在tsconfig.json中设置experimentalDecorators属性。除非你正在使用Angular或其它需要注解的框架,并且在它们被标准化之前,不要使用TypeScript的装饰器。