TypeScript入门
添加了类型系统的 JavaScript,适用于任何规模的项目
# 基本
# 简单数据类型
let isDone: boolean = false;
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let myName: string = "Tom";
let u: undefined = undefined;
let n: null = null;
// JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数:
function alertName(): void {
alert("My name is Tom");
}
//任意值
let myFavoriteNumber: any = "seven";
myFavoriteNumber = 7;
//变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
let something;
something = "seven";
something = 7;
something.setName("Tom");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 类型推论
如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。
let myFavoriteNumber: string = "seven";
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
1
2
3
4
2
3
4
# 联合类型
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型
let myFavoriteNumber: string | number;
myFavoriteNumber = "seven";
myFavoriteNumber = 7;
1
2
3
2
3
# 复杂数据类型
// 最简单的方法是使用「类型 + 方括号」来表示数组:
let fibonacci: number[] = [1, 1, 2, 3, 5];
// 我们也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组:
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
// 一个比较常见的做法是,用 any 表示数组中允许出现任意类型:
let list: any[] = ["xcatliu", 25, { website: "http://xcatliu.com" }];
1
2
3
4
5
6
2
3
4
5
6
// 限制函数参数和返回值类型
function sum(x: number, y: number): number {
return x + y;
}
// 可选参数
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + " " + lastName;
} else {
return firstName;
}
}
// 参数默认值
function buildName(firstName: string, lastName: string = "Cat") {
return firstName + " " + lastName;
}
// 剩余参数
function push(array, ...items) {
items.forEach(function (item) {
array.push(item);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 接口
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: "Tom",
age: 25,
};
// 可选属性
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: "Tom",
};
// 任意属性
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: "Tom",
gender: "male",
};
//只读
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: "Tom",
gender: "male",
};
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
39
40
41
42
43
44
45
46
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
39
40
41
42
43
44
45
46
# 继承
interface Error {
name: string;
code: number;
}
interface ApiError extends Error {
code2: number;
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型
- 联合类型可以被断言为其中一个类型
- 父类可以被断言为子类
- 任何类型都可以被断言为 any
- any 可以被断言为任何类型
# 声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能,通常我们会把声明语句放到一个单独的文件(jQuery.d.ts)中,这就是声明文件
declare var声明全局变量declare function声明全局方法declare class声明全局类declare enum声明全局枚举类型declare namespace声明(含有子属性的)全局对象interface和type声明全局类型export导出变量export namespace导出(含有子属性的)对象export defaultES6 默认导出export = commonjs导出模块export as namespaceUMD 库声明全局变量declare global扩展全局变量declare module扩展模块/// <reference />三斜线指令
// src/jQuery.d.ts
declare let jQuery: (selector: string) => any;
// src/Directions.d.ts
declare enum Directions {
Up,
Down,
Left,
Right,
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 进阶
# 类型别名
类型别名用来给一个类型起个新名字。
// 首先说下可以用接口重写类型别名
type People = {
name: string;
age: number;
};
// 可以重写为下面这个接口
interface People {
name: string;
age: number;
}
// 现在二者完全一样,任何使用类型别名 People 的地方都可以使用接口 People
// 第一个区别
// 类型别名右边可以是多种类型,包括类型、|、&等
type A = number;
type B = A | string;
// 接口只能为类型
interface A {
name: string;
}
// 第二个区别
// 扩展接口时会检查是否可被赋值
interface A {
func(x: number): number;
}
interface B extends A {
func(x: string): string;
}
// 如果 B 继承 A 会报错
// 而如果把接口写成类型别名,extends 换成 & 则不会报错,别名会重载这个 func
// 第三个区别
// 同一作用域下的多个同名接口会自动合并,而类型别名则会报错,这个就是声明合并
interface People {
name: string;
}
interface People {
age: number;
}
// People在同一作用下会合并如下
interface People {
name: string;
age: number;
}
// 如果将 interface 换成 type 则会报错
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
39
40
41
42
43
44
45
46
47
48
49
50
51
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
39
40
41
42
43
44
45
46
47
48
49
50
51
# 字符串字面量类型
type EventNames = "click" | "scroll" | "mousemove";
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById("hello"), "scroll"); // 没问题
handleEvent(document.getElementById("world"), "dblclick"); // 报错,event 不能为 'dblclick'
1
2
3
4
5
6
7
2
3
4
5
6
7
# 元祖
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
let tom: [string, number] = ["Tom", 25];
let tom: [string, number];
tom[0] = "Tom";
tom[1] = 25;
tom[0].slice(1);
tom[1].toFixed(2);
// 当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型:
let tom: [string, number];
tom = ["Tom", 25];
tom.push("male");
tom.push(true);
// Argument of type 'true' is not assignable to parameter of type 'string | number'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 枚举
枚举(Enum)类型用于取值被限定在一定范围内的场景
enum Days {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat,
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 类
JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了 class
# ES 中的用法
class Animal {
public name;
constructor(name) {
this.name = name;
}
sayHi() {
return `My name is ${this.name}`;
}
}
let a = new Animal("Jack");
console.log(a.sayHi()); // My name is Jack
// 使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。
class Cat extends Animal {
constructor(name) {
super(name); // 调用父类的 constructor(name)
console.log(this.name);
}
sayHi() {
return "Meow, " + super.sayHi(); // 调用父类的 sayHi()
}
}
let c = new Cat("Tom"); // Tom
console.log(c.sayHi()); // Meow, My name is Tom
// 使用 getter 和 setter 可以改变属性的赋值和读取行为:
class Animal {
constructor(name) {
this.name = name;
}
get name() {
return "Jack";
}
set name(value) {
console.log("setter: " + value);
}
}
let a = new Animal("Kitty"); // setter: Kitty
a.name = "Tom"; // setter: Tom
console.log(a.name); // Jack
// 使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用
class Animal {
static isAnimal(a) {
return a instanceof Animal;
}
}
let a = new Animal("Jack");
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function
// ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义:
class Animal {
name = "Jack";
constructor() {
// ...
}
}
let a = new Animal();
console.log(a.name); // Jack
// ES7 提案中,可以使用 static 定义一个静态属性:
class Animal {
static num = 42;
constructor() {
// ...
}
}
console.log(Animal.num); // 42
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# TypeScript 中的用法
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal("Jack");
console.log(a.name); // Jack
a.name = "Tom";
console.log(a.name); // Tom
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'
class Animal {
protected name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
// 修饰符和readonly还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁。
class Animal {
// public name: string;
public constructor(public name) {
// this.name = name;
}
}
class Animal {
// public readonly name;
public constructor(public readonly name) {
// this.name = name;
}
}
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
abstract 用于定义抽象类和其中的抽象方法。
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal("Jack");
// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.
// 抽象类中的抽象方法必须被子类实现:
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public sayHi() {
console.log(`Meow, My name is ${this.name}`);
}
}
let cat = new Cat("Tom");
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
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
# 类与接口
# 类实现接口
上次更新: 2024/01/18, 10:44:15