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-在此处。

最后

希望本文会对大家有所帮助,大家能喜欢这篇文章。