TypeScript可编译为JavaScript,专为开发大型和复杂应用程序的开发人员设计。TypeScript从C#和Java这样的语言继承了许多编程概念,为强大灵活、弱类型的JavaScript增加了更多的强类型语言的特性。
本文适用于熟悉JavaScript同时想要了解TypeScript的人,将介绍语言基本特性和关键功能,并将提供带注释的代码示例,冀希望有助于了解TypeScript。以下为正文。
使用TypeScript的好处
JavaScript适用范围很广,了解Javascript的情况下还真的需要学习TypeScript吗?从技术上讲,不需要学习TypeScript就可以成为一名优秀的开发人员,不用学习Typtescript也可以。不过,学习TypeScript带来的好处也很多:
- 由于是静态类型,用TypeScript编写的代码更容易把控、更易于调试。
- 借助于模块、名称空间和强大的OOP支持,Typescript让大型、复杂的应用程序组织代码更容易。
- TypeScript对JavaScript有一个编译步骤,可在此编译过程发现各种错误,运行时前就发现代码错误
- Angular 2框架是用TypeScript编写的,建议开发人员可考虑在项目中使用该语言
还有一点,这一点对很多人来说很重要,也是大家使用TypeScript的主要原因。Angular 2是目前最热门的框架之一,尽管开发人员可以将其与常规JavaScript一起使用,但大多数教程和示例都是用TS编写的。随着Angular 2社区的发展,会有越来越多的人选择TypeScript。
来自Google趋势的数据显示TypeScript的近期流行度
安装TypeScript
本教程将需要Node.js和Npm。如果尚未安装,请参见这里。
安装TypeScript的最简单方法是通过npm。使用以下命令,可全局安装TypeScript软件包,以便TS编译器可用于所有的项目:
npm install
接下来可以打开终端并运行tsc -v,查看其是否已正确安装。
tsc -v
Version 1.8.10
支持TypeScript的文本编辑器
TypeScript是开源项目,由Microsoft开发和维护,最初仅在Microsoft的Visual Studio平台中得到支持。如今,有许多文本编辑器和IDE都支持TypeScript语法、支持Auto compelete、支持错误检查、甚至内置编译器。
- Visual Studio Code -Microsoft的另一个轻量级开源代码编辑器。内置TypeScript支持。
- Sublime Text的官方免费插件。
- WebStorm的最新版本带有内置支持。
- 更多,包括Vim,Atom,Emacs等。
编译为JavaScript
TypeScript代码文件扩展名是.ts(JSX代码是.tsx),不能直接在浏览器中运行,需要先翻译为原始的.js。编译过程可通过多种不同方式完成:
- 终端中使用前面提到的命令行工具tsc。
- 直接在Visual Studio或其IDE和它文本编辑器中。
- 使用gulp这样的自动化构建任务工具。
第一种方式最简单、最方便,将在本文中使用。
下面这条命令将main.ts转换为JavaScript版本main.js。如main.js已经存在,将被覆盖。
tsc main
也可以列出所有文件,或应用通配符来一次编译多个文件:
# Will result in separate .js files: main.js worker.js.
tsc main.ts worker.ts
# Compiles all .ts files in the current folder. Does NOT work recursively.
tsc *.ts
加入--watch选项后,可以自动编译TypeScript文件:
# Initializes a watcher process that will keep main.js up to date.
tsc main.ts --watch
有经验的TypeScript用户可创建tsconfig.json文件,包含各种构建设置。处理大量.ts文件的大型项目时,配置文件非常方便,可以自动完成构建过程。可在此处的TypeScript文档中了解到有关tsconfig.json的更多信息
静态类型
TypeScript的一大特点是对静态类型的支持,开发人员可以声明变量的类型,编译器在运行时保证为变量分配正确的值类型。代码里如声明时类型省略,将从代码自动推断类型。
下面的代码样例能看到,变量、函数参数、返回值均可在初始化时定义其类型:
var burger: string = 'hamburger', // String
calories: number = 300, // Numeric
tasty: boolean = true; // Boolean
// Alternatively, you can omit the type declaration:
// var burger = 'hamburger';
// The function expects a string and an integer.
// It doesn't return anything so the type of the function itself is void.
function speak(food: string, energy: number): void {
console.log("Our " + food + " has " + energy + " calories.");
}
speak(burger, calories);
TypeScript会编译为JavaScript,而Javascript不需要知道类型信息,所以在编译生成的Javascript代码里类型信息会删除:
// JavaScript code from the above TS example.
var burger = 'hamburger',
calories = 300,
tasty = true;
function speak(food, energy) {
console.log("Our " + food + " has " + energy + " calories.");
}
speak(burger, calories);
但是,如果在写TypeScript代码时试着写一些有语法错误的代码,编译时tsc会警告我们代码中存在错误。例如:
// The given type is boolean, the provided value is a string.
var tasty: boolean = "I haven't tried it yet";
main.ts(1,5): error TS2322: Type 'string' is not assignable to type 'boolean'.
如将错误的参数传递给函数,也会收到错误提示:
function speak(food: string, energy: number): void{
console.log("Our " + food + " has " + energy + " calories.");
}
// Arguments don't match the function parameters.
speak("tripple cheesburger", "a ton of");
main.ts(5,30):TS2345:of'string'of'number'.
下面是最常用的几种数据类型:
- Number-所有数值均由数字类型表示,没有整数,浮点数或其他定义。
- String-文本类型,就像在普通JS字符串中一样,可以用“单引号”或“双引号”括起来。
- Boolean- true或false,使用0和1将导致编译错误。
- Any-这种类型的变量可以将其值设置为字符串,数字或其它任何东西。
- Arrays-具有两种可能的语法:my_arr: number[];或my_arr: Array<number>。
- Void-用于不返回任何内容的函数。
要查看所有可用类型的列表,请访问官方TypeScript文档。
接口
接口用于检查对象是否适合于某个特定结构。通过接口定义,可以指定变量的特定组合,保证这样的这是组合会被一起使用。转换为JavaScript后,接口就消失了。接口存在的唯一目的就是在开发阶段为开发人员提供帮助。
下面的示例,定义了一个简单接口对函数的参数进行类型检查:
// Here we define our Food interface, its properties, and their types.
interface Food {
name: string;
calories: number;
}
// We tell our function to expect an object that fulfills the Food interface.
// This way we know that the properties we need will always be available.
function speak(food: Food): void{
console.log("Our " + food.name + " has " + food.calories + " calories.");
}
// We define an object that has all of the properties the Food interface expects.
// Notice that types will be inferred automatically.
var ice_cream = {
name: "ice cream",
calories: 200
}
speak(ice_cream);
属性的顺序无关紧要,需要属性需要存在、类型正确。如部分缺失、类型错误或名称不对,编译器就会报错。
interface Food {
name: string;
calories: number;
}
function speak(food: Food): void{
console.log("Our " + food.name + " has " + food.calories + " grams.");
}
// We've made a deliberate mistake and name is misspelled as nmae.
var ice_cream = {
nmae: "ice cream",
calories: 200
}
speak(ice_cream);
main.ts(16,7): error TS2345: Argument of type '{ nmae: string; calories: number; }
is not assignable to parameter of type 'Food'.
Property 'name' is missing in type '{ nmae: string; calories: number; }'.
本文用于TypeScript初步介绍,将不对接口进行更详细的说明。关于接口,需要了解的内容比这里提到的要多得多,建议查看TypeScript文档。
类
构建大型应用程序的时候,许多开发人员都倾向于使用面向对象的编程风格,尤其是Java或C#等语言。TypeScript提供了一种与这些语言非常相似的类系统,包括继承、抽象类、接口实现、setters / getters等。
需要说明的是,最新的JavaScript更新(ECMAScript 2015)里,类是原生JS的一部分,可在不使用TypeScript的情况下在Javascript里使用类。两种实现非常相似,但有还是有所区别:TypeScript的类更为严格。
还是使用上面关于Food的场景,下面是一个简单的TypeScript类:
class Menu {
// Our properties:
// By default they are public, but can also be private or protected.
items: Array<string>; // The items in the menu, an array of strings.
pages: number; // How many pages will the menu be, a number.
// A straightforward constructor.
constructor(item_list: Array<string>, total_pages: number) {
// The this keyword is mandatory.
this.items = item_list;
this.pages = total_pages;
}
// Methods
list(): void {
console.log("Our menu for today:");
for(var i=0; i<this.items.length; i++) {
console.log(this.items[i]);
}
}
}
// Create a new instance of the Menu class.
var sundayMenu = new Menu(["pancakes","waffles","orange juice"], 1);
// Call the list method.
sundayMenu.list();
写过Java或C#的人都会觉得这种语法很熟悉。对于继承的语法大家应该也会有这种熟悉的感觉:
class HappyMeal extends Menu {
// Properties are inherited
// A new constructor has to be defined.
constructor(item_list: Array<string>, total_pages: number) {
// In this case we want the exact same constructor as the parent class (Menu),
// To automatically copy it we can call super() - a reference to the parent's constructor.
super(item_list, total_pages);
}
// Just like the properties, methods are inherited from the parent.
// However, we want to override the list() function so we redefine it.
list(): void{
console.log("Our special menu for children:");
for(var i=0; i<this.items.length; i++) {
console.log(this.items[i]);
}
}
}
// Create a new instance of the HappyMeal class.
var menu_for_children = new HappyMeal(["candy","drink","toy"], 1);
// This time the log message will begin with the special introduction.
menu_for_children.list();
更多了解TS中的类,可参阅此文档。
泛型
泛型允许同一个函数以模板形式接受不同类型参数。相比起来,使用泛型创建可重用的组件比使用any数据类型更方便,因为泛型保留了输入、输出变量的类型。
下面的示例代码接受一个输入参数并返回包含相同参数的数组。
// The <T> after the function name symbolizes that it's a generic function.
// When we call the function, every instance of T will be replaced with the actual provided type.
// Receives one argument of type T,
// Returns an array of type T.
function genericFunc<T>(argument: T): T[] {
var arrayOfT: T[] = []; // Create empty array of type T.
arrayOfT.push(argument); // Push, now arrayOfT = [argument].
return arrayOfT;
}
var arrayFromString = genericFunc<string>("beep");
console.log(arrayFromString[0]); // "beep"
console.log(typeof arrayFromString[0]) // String
var arrayFromNumber = genericFunc(42);
console.log(arrayFromNumber[0]); // 42
console.log(typeof arrayFromNumber[0]) // number
第一次调用此函数时,将类型设置为字符串。这个操作并非必须要做,因为编译器能够判断传递的参数类型,可以自动确定哪种类型最适合,第二次调用即是如此。虽然泛型的类型指定不是强制性的,但每次諷用者提供类型都是一种良好的编码习惯,因为有可能在更为复杂的情况下编译器无法判断正确的类型。
TypeScript文档包括一些更复杂的示例,包括泛型类、与接口组合等等。您查看这里找到这些示例。
模块
大型应用程序的另一个重要概念是模块化。和单个10000行的代码文件相比,代码划分为许多可重用的小组件可以让项目更容易组织、代码可易于理解。
TypeScript引入了export和import模块的语法,但无法处理文件之间的实际连接。要启用外部模块,TS依赖于第三方库:浏览器应用程序为require.js,Node.js 为CommonJS。来看一个带有require.js的TypeScript模块的简单示例。
示例引入两个代码文件,一个文件export一个方法,另外一个文件import后调用此方法。
exporter.ts
var sayHi = function(): void {
console.log("Hello!");
}
export = sayHi;
importer.ts
import sayHi = require('./exporter');
sayHi();
需要下载require.js并将其包含在脚本标记中- 在此处查看操作方法。最后一步是编译这两个.ts文件。和CommonJS不一样之处在于,需要添加一个额外的参数来告诉TypeScript我们正在为require.js(也称为AMD)构建模块。
tsc --module amd *.ts
TypeScript模块支持的内容很多,其它内容超出了本文范围。如想继续了解,请访问TS文档。
第三方的声明文件
使用为JavaScript设计的库时,我们需要使用声明文件以使该库可以与TypeScript兼容。声明文件的扩展名为.d.ts,其中包含有关库及其API的各种信息。
TypeScript声明文件通常是手工编写的,但也有可能要用到的库已经有一个由其他人创建的.d.ts文件。DefinitelyTyped是最大的公共存储库,包含超过一千个库的文件。还有一个流行的Node.js模块,用于管理TypeScript定义,称为Typings。
如需自己编写声明文件,此指南将用于了解入门信息。
TypeScript 2.0中的新功能
TypeScript仍在积极开发中,并且会不断发展。撰写本教程时(译注:原文发表于2016年),LTS版本是1.8.10,但Microsoft已经发布了TypeScript 2.0 Beta。可以用于公共测试,需要尝试的话:
npm install -g typescript @ beta
TypeScript 2.0引入了一些的新概念,如:
- 非空类型标志,可防止某些变量将其值设置为null或undefined。
- 以通过npm install得到声明文件的改进的方式
- 控制流类型分析,可捕获编译器遗漏的错误
- 模块导出/导入语法中的一些创新
另一个期待已久的功能是控制async/await块中异步功能流的能力。在将来的2.1更新中应该可用。
进一步阅读
最初,官方文档中的信息量可能有点让人不知所措,但坚持阅读将获得很大的好处。本文作为介绍性文字,没有涵盖TypeScript文档中的所有章节。以下是一些我们跳过的、但更有帮助的概念:
- 命名空间- 在这里。
- 枚举- 在这里。
- 高级类型和类型卫士(Type Guards)- 在这里。
- 在TypeScript中编写JSX-在此处。
最后
希望本文会对大家有所帮助,大家能喜欢这篇文章。