TypeScript(ts) 可以理解为 JavaScript 的超集。TypeScript 本身不能直接运行, 因为 JavaScript 引擎并不会认识 TypeScript, 所以他需要先编译成 JavaScript 代码, 然后才能运行。

数据类型

基本的数据类型与 js 一致,下面则几种则是比较特殊的:

enum

enum 即枚举类型

1
2
3
4
5
6
7
enum Shading {
Flat, // 0
Gouraud, // 1
Phong, // 2
}

let shadingType: Shading = Shading.Phong; // shadingType = 2

如上所示,枚举实际也是个 number 值,其每个成员的值从上往下依次递增,如果没有明确指定首个成员的值,那么默认为0.

此外还有两种特别的枚举:字符串枚举异构枚举

字符串枚举

在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

1
2
3
4
5
6
7
enum Shading {
Flat = "Flat",
Gouraud = "Gouraud",
Phong = "Phong",
}

let shadingType: Shading = Shading.Flat;

异构枚举

异构枚举的成员值是数字和字符串的混合
1
2
3
4
5
enum EnumExample {
A = 1,
S_A = "str",
B = 2,
}

Any

ts 中,任何类型都可以被归为 Any 类型因此其也叫全局超级类型可以把 typescript 写成 anyscript

1
2
3
let anyvar: any = "123";
anyvar = 5;
anyvar = true;

any 类型本质上是类型系统的一个逃逸舱。方便了开发者的同时也给程序带来了很多麻烦,如果滥用 any 的话那就真的成 anyscript。因此,后面引入了 unknown

unknown

any 一样的是,所有类型的值也可以赋给 unknown ,因此 unknown 也是 ts 中的一种顶级类型,例如:

1
2
3
4
5
6
7
8
let value: unknown;

value = 5;
value = {};
value = "564";
value = [];
value = null;
value = undefined;

可以看出对 unknown 的所有赋值操作都是没问题的,可是我们再看看使用 unknown 类型给其他类型赋值会怎么样
1
2
3
4
let value: unknown;
value = 6;
let value_number: number = value;
// error TS2322: Type 'unknown' is not assignable to type 'number'.

可以看到,无法将 number 类型分配给 number 类型,即使里面存储的是 number 类型。unknown 类型只能被赋值给 any 类型和 unknown 类型本身。

tuple

tuple 即元祖,其可以存储各种不同的类型。元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值。

使用过其他语言的元祖时就很好理解了,例如 c++ 中使用 tuple 返回多个类型的值:

1
2
3
std::tuple<std::string, int, float> return_tuple() {
return {"123", 5, 0.1f};
}

同样在 ts 中也可以使用 tuple 写出相应的函数:
1
2
3
function return_tuple(): [string, number] {
return ["123", 123];
}

由此可知几下这点:

  • tuple 的声明类似于数组:
    1
    const tuple_value: [string, number] = ["456", 123];
  • tuple 声明类型后初始化时如果类型不同不能交换两个值的位置,也不能有空缺
    1
    2
    const tuple_value: [string, number] = [123, "456"]; // error
    const tuple_value: [string, number] = [123]; // error

void

void 类型表示它没有任何类型,当一个函数没有返回值时,就可以将其返回类型声明为 void

1
2
3
function return_void(): void {
console.log("in function return_void");
}

值得注意的是:声明一个 void 类型的变量并没用什么意义,因为其值只能为 undefined 或者 null
1
let void_value: void = null;

组合类型

ts 支持一个变量可以被赋予多种不同的类型,多个类型之间使用 | 号分割

1
2
3
4
let value: number | string;
value = 999; // ok
value = "123"; // ok
value = true; // error

never

never 类型表示不存在的类型,即表示永远不会发生的情况或不应该发生的情况。它通常用于表示错误或不可能到达的代码分支。

1
2
3
function throwError(message: string): never {
throw new Error(message);
}

还有一种使用方式就是配合 switch 语句去处理无法到达的分支
1
2
3
4
5
6
7
8
9
10
11
function print_value(value: string | number) {
switch (typeof value) {
case "string":
return `it's a string, value: ${value}`;
case "number":
return `it's a number, value: ${value}`;
default:
const _exhaustiveCheck: never = value;
return _exhaustiveCheck;
}
}

函数

声明

1
2
3
4
function functionName(agrs): retunrn_type {
// do something
[return return_value;]
}
1
2
3
4
let functionName = funciton(args): retunrn_type {
// do something
[return return_value;]
};

必选参数和可选参数

必选参数:顾名思义调用函数时必须参数的参数。且实参和形参的类型数量都得一致

1
2
3
function return_info_str(name: string, age: number): string {
return `name: ${name}; age: ${age}`;
}

可选参数:顾名思义可传可不传
1
2
3
function return_info_str(name: string, age?: number): string {
return `name: ${name}; age: ${age}`;
}

变长参数

可以接受不定长的参数,下面是一个使用变长参数实现 sum 函数的例子:

1
2
3
4
5
6
7
8
9
function sum(value: number, ...values: number[]): number {
let sum = value;
for (let x of values) {
sum += x;
}
return sum;
}

console.log(sum(1, 3, 4, 88)); // output 96

此处有一个必选参数是防止接收空参数的情况

class

诸如声明,构造函数之类的,和 js 是完全差不多的,只有在函数参数和返回值标明了一点类型,下面介绍几个比较特别的特性:

抽象类

抽象类指不能被实例化的类,是提供给其他类继承的

使用 abstract 关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类(也就是其子类)中实现,abstract抽象方法只能放在抽象类里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Animal {
name: string

constructor(name: string) {
this.name = name;
}

abstract eat(): void;
}

class Dog extends Animal{
constructor(name: string) {
super(name);
}

eat(): void {
console.log(`Dog ${this.name} is eating`);
}
}

let dog: Dog = new Dog("hhh");
dog.eat(); // Dog hhh is eating

多态

此处指的是动态多态,不是重载函数之类的静态多态(突然不知道 c++ 里面的这个概念 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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
abstract class Animal {
name: string

constructor(name: string) {
this.name = name;
}

abstract eat(): void;
}

class Dog extends Animal{
constructor(name: string) {
super(name);
}

eat(): void {
console.log(`Dog ${this.name} is eating`);
}
}

class Cat extends Animal{
constructor(name: string) {
super(name);
}

eat(): void {
console.log(`Cat ${this.name} is eating`);
}
}

function animal_eat(animal: Animal): void {
animal.eat();
}

let dog: Dog = new Dog("ddd");
let cat: Cat = new Cat("neko");
animal_eat(dog); // Dog ddd is eating
animal_eat(cat); // Cat neko is eating

接口

接口( Interfaces )是一种用于描述对象形状( Shape )的结构化类型。接口定义了一个对象应该包含哪些属性和方法,以及它们的类型和参数类型等信息。

属性类型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Person {
name: string;
age: number;
}

function print_person_info(person: Person): void {
console.log(`${person.name} is ${person.age} year old`);
}

const person: Person = {
name: "nico",
age: 16
}

print_person_info(person);
// nico is 16 year old

函数类型接口

我们定义了一个接口 Calculator,它描述了一个函数类型,该函数接受两个数字类型的参数 ab,并返回一个数字类型的值。然后,我们定义了两个函数 addsubtract,它们都满足 Calculator 接口的要求

1
2
3
4
5
6
interface Calculator {
(a: number, b: number): number;
}

const add: Calculator = (a, b) => a + b;
const subtract: Calculator = (a, b) => a - b;

泛型

泛型就是解决类、接口、方法的复用性、以及对不特定数据类型的支持。

泛型类

以下是一个简单使用泛型类的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Stack<T> {
private items: T[] = [];

push(item: T) {
this.items.push(item);
}

pop(): T | undefined {
return this.items.pop();
}

size(): number {
return this.items.length;
}
}

定义了一个泛型类 Stack,它包含一个类型参数 T。类中的 items 属性是一个类型为 T 的数组,它存储了栈中的元素。类中的 push 方法接受一个类型为 T 的参数 item,并将它添加到栈中。类中的 pop 方法弹出栈顶元素,并返回一个类型为 T | undefined 的值。类中的 size 方法返回当前栈的大小。

以下是一些使用的例子:

1
2
3
4
5
6
7
8
9
10
11
const stack1 = new Stack<number>();
stack1.push(1);
stack1.push(2);
console.log(stack1.pop()); // output 2
console.log(stack1.size()); // output 1

const stack2 = new Stack<string>();
stack2.push("hello");
stack2.push("world");
console.log(stack2.pop()); // output "world"
console.log(stack2.size()); // output 1

泛型接口

1
2
3
4
5
6
interface Map<T> {
[key: string]: T;
}

const map1: Map<number> = { a: 1, b: 2 };
const map2: Map<string> = { x: "hello", y: "world" };

定义了一个泛型接口 Map,它包含一个类型参数 T。接口中的索引签名 [key: string]: T 表示该接口可以用字符串类型的键索引任意类型的值