简介

TypeScript,简称TS,是微软开发的开源、跨平台的编程语言,是JavaScript的超集。它在最终阶段会被编译为JavaScript代码。它与JavaScript的关系如下图所示:

ts2.png

从技术上讲,TS就是具有静态类型的JavaScript。其最大的特点就是强类型,支持静态和动态类型,可以在编译期间发现并纠正错误,降低试错成本,同时提升代码的规范性。

TS安装

1. 安装node.js

安装完node后,会自带一个npm包管理器,后面就可以通过npm来安装TS和其他库了。

node.js官网地址:https://nodejs.org/zh-cn

2. npm

输入npm -v可以查看npm的版本。由于npm下载插件是从国外服务器下载的,在国内速度较慢,大家可以替换为淘宝团队维护的镜像:

1
npm install -g cnpm -registry=https://registry.npm.taobao.org

然后使用cnpm -v查看版本,检测是否安装成功。

3. 全局安装TypeScript

1
npm install -g typescript

安装完成后运行tsc -V检查是否安装成功。

4. 编译

TS文件需要先编译为js文件才能被浏览器运行。可以手动编译:

1
tsc xx.ts

或者在VS Code上自动编译:

  • 在工作目录下执行 tsc –init,生成配置文件 tsconfig.json。
  • 修改 tsconfig.json 配置:outDir改为你想要存放编译后js文件的目录。
  • 在VS Code中启动监视任务:终端 -> 运行任务 -> 显示所有任务 -> 监视 tsconfig.json。

语法特性

1. 类型注解

轻量级的为函数或变量添加约束的方式。

1
2
3
4
5
6
7
(() => {
function showMsg(journal: string[]) {
return "三大期刊:" + journal.join(',');
}
let journals = ["Nature", "Science", "Cell"];
document.body.innerHTML = showMsg(journals);
})();

2. 接口

在TS里,只要两个类型内部的结构兼容,那么这两个类型就是兼容的。在实现接口时,只需要保证包含了接口要求的结构就可以,而不必明确的使用implements语句。

1
2
3
4
5
6
7
8
9
10
11
12
(() => {
interface Person {
userName: string;
age: number;
skill: string
}
function greeter(person: Person) {
return `姓名:${person.userName},年龄:${person.age},技能:${person.skill}`;
}
let user = { userName: "小A", age: 35, skill: "TS" };
document.body.innerHTML = greeter(user);
})();

3. 类

TS支持基于类的面向对象编程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(() => {
class Student {
msg: string;
userName: string;
age: number;
skill: string;
constructor(userName: string, age: number, skill: string) {
this.userName = userName;
this.age=age;
this.skill = skill;
this.msg = userName + age + skill;
}
}
interface Person {
userName: string;
age: number,
skill: string;
}
function greeter(person: Person) {
return "姓名: " + person.userName + "年龄:" + person.age + ",技能 " + person.skill;
}
let user = new Student("小A", 35, "TS");
document.body.innerHTML = greeter(user);
})();

常用语法

1. 基础类型

数据类型 关键字 描述
任意类型 any 声明为 any 的变量可以赋予任意类型的值。
数字类型 number 双精度 64 位浮点值。它可以用来表示整数和分数。
let binaryLiteral: number = 0b1010; // 二进制 let octalLiteral: number = 0o744; // 八进制 let decLiteral: number = 6; // 十进制 let hexLiteral: number = 0xf00d; // 十六进制
字符串类型 string 一个字符系列,使用单引号(**’)或双引号(“**)来表示字符串类型。反引号(`)来定义多行文本和内嵌表达式。
let name: string = "Runoob"; let years: number = 5; let words: string = \`您好,今年是 ${ name } 发布 ${ years + 1} 周年\`;
布尔类型 boolean 表示逻辑值:true 和 false。let flag: boolean = true;
数组类型 声明变量为数组。
// 在元素类型后面加上[] let arr: number[] = [1, 2]; // 或者使用数组泛型 let arr: Array<number> = [1, 2];
元组 元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。
let x: [string, number]; x = ['Runoob', 1]; // 运行正常 x = [1, 'Runoob']; // 报错 console.log(x[0]); // 输出 Runoob
枚举 enum 枚举类型用于定义数值集合。
enum Color {Red, Green, Blue}; let c: Color = Color.Blue; console.log(c); // 输出 2
void void 用于标识方法返回值的类型,表示该方法没有返回值。
function hello(): void {alert("Hello Runoob"); }
null null 表示对象值缺失。
undefined undefined 用于初始化变量为一个未定义的值
never never never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。

注意:TypeScript 和 JavaScript 没有整数类型。

2. 变量声明

变量是一种使用方便的占位符,用于引用计算机内存地址。

我们可以把变量看做存储数据的容器。

TypeScript 变量的命名规则:

  • 变量名称可以包含数字和字母。
  • 除了下划线 _ 和美元 $ 符号外,不能包含其他特殊字符,包括空格。
  • 变量名不能以数字开头。

变量使用前必须先声明,我们可以使用 var 来声明变量。

我们可以使用以下四种方式来声明变量:

声明变量的类型及初始值:

1
var [变量名] : [类型] = 值;

例如:

1
var uname:string = "Runoob";

声明变量的类型,但没有初始值,变量值会设置为 undefined:

1
var [变量名] : [类型];

例如:

1
var uname:string;

声明变量并初始值,但不设置类型,该变量可以是任意类型:

1
var [变量名] = 值;

例如:

1
var uname = "Runoob";

声明变量没有设置类型和初始值,类型可以是任意类型,默认初始值为 undefined:

1
var [变量名];

例如:

1
var uname;

3. 类型断言

类型断言可以用来手动指定一个值的类型,即允许变量从一种类型更改为另一种类型。

语法格式:

1
<类型>值

或:

1
as 类型

实例

1
2
3
var str = '1'  
var str2:number = <number> <any> str //str、str2 是 string 类型
console.log(str2)

4. 类型推断

当类型没有给出时,TypeScript 编译器利用类型推断来推断类型。

如果由于缺乏声明而不能推断出类型,那么它的类型被视作默认的动态 any 类型。

1
2
3
4
var num = 2;    // 类型推断为 number 
console.log("num 变量的值为 "+num);
num = "12"; // 编译错误
console.log(num);
  • 第一行代码声明了变量 num 并=设置初始值为 2。 注意变量声明没有指定类型。因此,程序使用类型推断来确定变量的数据类型,第一次赋值为 2,num 设置为 number 类型。

  • 第三行代码,当我们再次为变量设置字符串类型的值时,这时编译会错误。因为变量已经设置为了 number 类型。

    1
    error TS2322: Type '"12"' is not assignable to type 'number'.

5. 联合类型

联合类型(Union Types)可以通过管道(|)将变量设置多种类型,赋值时可以根据设置的类型来赋值。

注意:只能赋值指定的类型,如果赋值其它类型就会报错。

创建联合类型的语法格式如下:

1
Type1|Type2|Type3 

6. 条件语句

通常在写代码时,您总是需要为不同的决定来执行不同的动作。您可以在代码中使用条件语句来完成该任务。

在 TypeScript 中,我们可使用以下条件语句:

  • if 语句 - 只有当指定条件为 true 时,使用该语句来执行代码
  • if…else 语句 - 当条件为 true 时执行代码,当条件为 false 时执行其他代码
  • if…else if….else 语句- 使用该语句来选择多个代码块之一来执行
  • switch 语句 - 使用该语句来选择多个代码块之一来执行

if 语句

TypeScript if 语句由一个布尔表达式后跟一个或多个语句组成。

语法格式如下所示:

1
2
3
if (boolean_expression) {
# 在布尔表达式 boolean_expression 为 true 执行
}

如果布尔表达式 boolean_expression为 true,则 if 语句内的代码块将被执行。如果布尔表达式为 false,则 if 语句结束后的第一组代码(闭括号后)将被执行。

if…else 语句

一个 if 语句后可跟一个可选的 else 语句,else 语句在布尔表达式为 false 时执行。

语法格式如下所示:

1
2
3
4
5
if (boolean_expression) {
# 在布尔表达式 boolean_expression 为 true 执行
} else {
# 在布尔表达式 boolean_expression 为 false 执行
}

如果布尔表达式 boolean_expression 为 true,则执行 if 块内的代码。如果布尔表达式为 false,则执行 else 块内的代码。

if…else if….else 语句

if…else if….else 语句在执行多个判断条件的时候很有用。

语法格式如下所示:

1
2
3
4
5
6
7
8
9
if (boolean_expression 1) {
# 在布尔表达式 boolean_expression 1true 执行
} else if ( boolean_expression 2) {
# 在布尔表达式 boolean_expression 2true 执行
} else if ( boolean_expression 3) {
# 在布尔表达式 boolean_expression 3true 执行
} else {
# 布尔表达式的条件都为 false 时执行
}

需要注意以下几点:

  • 一个 if 判断语句可以有 0 或 1 个 else 语句,她必需在 else..if 语句后面。
  • 一个 if 判断语句可以有 0 或多个 else..if,这些语句必需在 else 之前。
  • 一旦执行了 else..if 内的代码,后面的 else..ifelse 将不再执行。

switch…case 语句

一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。

switch 语句的语法:

1
2
3
4
5
6
7
8
9
10
11
12
switch (expression) {    
case constant-expression :
statement(s);
break; /* 可选的 */
case constant-expression :
statement(s);
break; /* 可选的 */

/* 您可以有任意数量的 case 语句 */
default : /* 可选的 */
statement(s);
}

switch 语句必须遵循下面的规则:

  • switch 语句中的 expression 是一个要被比较的表达式,可以是任何类型,包括基本数据类型(如 number、string、boolean)、对象类型(如 object、Array、Map)以及自定义类型(如 class、interface、enum)等。
  • 在一个 switch 中可以有任意数量的 case 语句。每个 case 后跟一个要比较的值和一个冒号。
  • case 的 constant-expression 必须与 switch 中的变量 expression 具有相同或兼容的数据类型。
  • 当被测试的变量等于 case 中的常量时,case 后跟的语句将被执行,直到遇到 break 语句为止。
  • 当遇到 break 语句时,switch 终止,控制流将跳转到 switch 语句后的下一行。
  • 不是每一个 case 都需要包含 break。如果 case 语句不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止。
  • 一个 switch 语句可以有一个可选的 default case,出现在 switch 的结尾。default 关键字则表示当表达式的值与所有 case 值都不匹配时执行的代码块。default case 中的 break 语句不是必需的。

7. 循环

有的时候,我们可能需要多次执行同一块代码。一般情况下,语句是按顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。

编程语言提供了更为复杂执行路径的多种控制结构。

for 循环

TypeScript for 循环用于多次执行一个语句序列,简化管理循环变量的代码。

语法格式如下所示:

1
2
3
for ( init; condition; increment ){
statement(s);
}

下面是 for 循环的控制流程解析:

  1. init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。
  2. 接下来,会判断 condition。如果为 true,则执行循环主体。如果为 false,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。
  3. 在执行完 for 循环主体后,控制流会跳回上面的 increment 语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。
  4. 条件再次被判断。如果为 true,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为 false 时,for 循环终止。

在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。

condition 可以是任意的表达式,当条件为 true 时执行循环,当条件为 false 时,退出循环。

for…in 循环

for…in 语句用于一组值的集合或列表进行迭代输出。

语法格式如下所示:

1
2
3
for (var val in list) { 
//语句
}

val 需要为 string 或 any 类型。

for…of 、forEach、every 和 some 循环

此外,TypeScript 还支持 for…of 、forEach、every 和 some 循环。

for…of 语句创建一个循环来迭代可迭代的对象。在 ES6 中引入的 for…of 循环,以替代 for…in 和 forEach() ,并支持新的迭代协议。for…of 允许你遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等。

for…of 循环

1
2
3
4
5
let someArray = [1, "string", false];  
for (let entry of someArray) {
console.log(entry);
// 1, "string", false
}

forEach、every 和 some 是 JavaScript 的循环语法,TypeScript 作为 JavaScript 的语法超集,当然默认也是支持的。

因为 forEach 在 iteration 中是无法返回的,所以可以使用 every 和 some 来取代 forEach。

forEach 循环

1
2
3
4
5
6
let list = [4, 5, 6]; 
list.forEach((val, idx, array) => {
// val: 当前值
// idx:当前index
// array: Array
});

every 循环

1
2
3
4
5
6
7
8
9
let list = [4, 5, 6]; 
list.every((val, idx, array) => {
// val: 当前值
// idx:当前index
// array: Array
return true;
// Continues
// Return false will quit the iteration
});

while 循环

while 语句在给定条件为 true 时,重复执行语句或语句组。循环主体执行之前会先测试条件。

语法格式如下所示:

1
2
3
4
while (condition)
{
statement(s);
}

在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。

condition 可以是任意的表达式,当条件为 true 时执行循环。 当条件为 false 时,程序流将退出循环。

do…while 循环

不像 forwhile 循环,它们是在循环头部测试循环条件。do…while 循环是在循环的尾部检查它的条件。

语法格式如下所示:

1
2
3
4
do
{
statement(s);
} while ( condition );

请注意,条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次。

如果条件为 true,控制流会跳转回上面的 do,然后重新执行循环中的 statement(s)。这个过程会不断重复,直到给定条件变为 false 为止。

break 语句

break 语句有以下两种用法:

  1. break 语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。
  2. 它可用于终止 switch 语句中的一个 case。

如果您使用的是嵌套循环(即一个循环内嵌套另一个循环),break 语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码。

continue 语句

continue 语句有点像 break 语句。但它不是强制终止,continue 会跳过当前循环中的代码,强迫开始下一次循环。

对于 for 循环,continue 语句执行后自增语句仍然会执行。对于 whiledo…while 循环,continue 语句重新执行条件判断语句。

无限循环

无限循环就是一直在运行不会停止的循环。 for 和 while 循环都可以创建无限循环。

for 创建无限循环语法格式:

1
2
3
for (;;) { 
// 语句
}

实例

1
2
3
for (;;) { 
console.log("这段代码会不停的执行")
}

while 创建无限循环语法格式:

1
2
3
while (true) { 
// 语句
}

实例

1
2
3
while (true) { 
console.log("这段代码会不停的执行")
}

8. 函数

函数是一组一起执行一个任务的语句。

您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的,但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。

函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。

函数就是包裹在花括号中的代码块,前面使用了关键词 function:

语法格式如下所示:

1
2
3
4
function function_name()
{
// 执行代码
}

带参数函数

在调用函数时,您可以向其传递值,这些值被称为参数。

这些参数可以在函数中使用。

您可以向函数发送多个参数,每个参数使用逗号 , 分隔:

语法格式如下所示:

1
2
function func_name( param1 [:datatype], param2 [:datatype]) {   
}
  • param1、param2 为参数名。
  • datatype 为参数类型。

可选参数、默认参数、剩余参数

可选参数

1
2
3
4
5
6
7
8
9
10
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}

let result1 = buildName("Bob"); // 正确
let result2 = buildName("Bob", "Adams", "Sr."); // 错误,参数太多了
let result3 = buildName("Bob", "Adams"); // 正确

默认参数

1
2
3
4
5
6
function calculate_discount(price:number,rate:number = 0.50) { 
var discount = price * rate;
console.log("计算结果: ",discount);
}
calculate_discount(1000)
calculate_discount(1000,0.30)

剩余参数

1
2
3
4
5
6
7
8
9
10
11
function addNumbers(...nums:number[]) {  
var i;
var sum:number = 0;

for(i = 0;i<nums.length;i++) {
sum = sum + nums[i];
}
console.log("和为:",sum)
}
addNumbers(1,2,3)
addNumbers(10,10,10,10,10)

匿名函数

匿名函数是一个没有函数名的函数。

匿名函数在程序运行时动态声明,除了没有函数名外,其他的与标准函数一样。

我们可以将匿名函数赋值给一个变量,这种表达式就成为函数表达式。

语法格式如下:

1
var res = function( [arguments] ) { ... }

构造函数

TypeScript 也支持使用 JavaScript 内置的构造函数 Function() 来定义函数:

语法格式如下:

1
var res = new Function ([arg1[, arg2[, ...argN]],] functionBody)

参数说明:

  • arg1, arg2, … argN:参数列表。
  • functionBody:一个含有包括函数定义的 JavaScript 语句的字符串。

Lambda 函数

Lambda 函数也称之为箭头函数。

箭头函数表达式的语法比函数表达式更短。

函数只有一行语句:

1
( [param1, param2,…param n] ) => statement;

函数重载

重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

参数类型不同:

1
2
function disp(string):void; 
function disp(number):void;

参数数量不同:

1
2
function disp(n1:number):void; 
function disp(x:number,y:number):void;

参数类型顺序不同:

1
2
function disp(n1:number,s1:string):void; 
function disp(s:string,n:number):void;

如果参数类型不同,则参数类型应设置为 any

参数数量不同你可以将不同的参数设置为可选。

9. 接口

接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。

TypeScript 接口定义如下:

1
2
interface interface_name { 
}

10. 类

TypeScript 是面向对象的 JavaScript。

类描述了所创建的对象共同的属性和方法。

TypeScript 支持面向对象的所有特性,比如 类、接口等。

TypeScript 类定义方式如下:

1
2
3
class class_name { 
// 类作用域
}

定义类的关键字为 class,后面紧跟类名,类可以包含以下几个模块(类的数据成员):

  • 字段 − 字段是类里面声明的变量。字段表示对象的有关数据。
  • 构造函数 − 类实例化时调用,可以为类的对象分配内存。
  • 方法 − 方法为对象要执行的操作。

11. 对象

对象是包含一组键值对的实例。 值可以是标量、函数、数组、对象等,如下实例:

1
2
3
4
5
6
7
8
var object_name = {     
key1: "value1", // 标量
key2: "value",
key3: function() {
// 函数
},
key4:["content1", "content2"] //集合
}

以上对象包含了标量,函数,集合(数组或元组)。

12. 命名空间

命名空间一个最明确的目的就是解决重名问题。

假设这样一种情况,当一个班上有两个名叫小明的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的姓(王小明,李小明),或者他们父母的名字等等。

命名空间定义了标识符的可见范围,一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的命名空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他命名空间中。

TypeScript 中命名空间使用 namespace 来定义,语法格式如下:

1
2
3
4
namespace SomeNameSpaceName {
export interface ISomeInterfaceName { }
export class SomeClassName { }
}

以上定义了一个命名空间 SomeNameSpaceName,如果我们需要在外部可以调用 SomeNameSpaceName 中的类和接口,则需要在类和接口添加 export 关键字。

要在另外一个命名空间调用语法格式为:

1
SomeNameSpaceName.SomeClassName;

如果一个命名空间在一个单独的 TypeScript 文件中,则应使用三斜杠 /// 引用它,语法格式如下:

1
/// <reference path = "SomeFileName.ts" />

13. 模块

TypeScript 模块的设计理念是可以更换的组织代码。

模块是在其自身的作用域里执行,并不是在全局作用域,这意味着定义在模块里面的变量、函数和类等在模块外部是不可见的,除非明确地使用 export 导出它们。类似地,我们必须通过 import 导入其他模块导出的变量、函数、类等。

两个模块之间的关系是通过在文件级别上使用 import 和 export 建立的。

模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于 Node.js 的 CommonJS 和服务于 Web 应用的 Require.js。

此外还有有 SystemJs 和 Webpack。

模块导出使用关键字 export 关键字,语法格式如下:

1
2
3
4
// 文件名 : SomeInterface.ts
export interface SomeInterface {
// 代码部分
}

要在另外一个文件使用该模块就需要使用 import 关键字来导入:

1
import someInterfaceRef = require("./SomeInterface");

14. 声明文件

TypeScript 作为 JavaScript 的超集,在开发过程中不可避免要引用其他第三方的 JavaScript 的库。虽然通过直接引用可以调用库的类和方法,但是却无法使用TypeScript 诸如类型检查等特性功能。为了解决这个问题,需要将这些库里的函数和方法体去掉后只保留导出类型声明,而产生了一个描述 JavaScript 库和模块信息的声明文件。通过引用这个声明文件,就可以借用 TypeScript 的各种特性来使用库文件了。

假如我们想使用第三方库,比如 jQuery,我们通常这样获取一个 id 是 foo 的元素:

1
2
3
$('#foo');
// 或
jQuery('#foo');

但是在 TypeScript 中,我们并不知道 $ 或 jQuery 是什么东西:

1
2
3
jQuery('#foo');

// index.ts(1,1): error TS2304: Cannot find name 'jQuery'.

这时,我们需要使用 declare 关键字来定义它的类型,帮助 TypeScript 判断我们传入的参数类型对不对:

1
2
3
declare var jQuery: (selector: string) => any;

jQuery('#foo');

declare 定义的类型只会用于编译时的检查,编译结果中会被删除。

上例的编译结果是:

1
jQuery('#foo');

声明文件以 .d.ts 为后缀,例如:

1
runoob.d.ts

声明文件或模块的语法格式如下:

1
2
declare module Module_Name {
}

TypeScript 引入声明文件语法格式:

1
/// <reference path = " runoob.d.ts" />

当然,很多流行的第三方库的声明文件不需要我们定义了,比如 jQuery 已经有人帮我们定义好了