# TypeScript快速学习笔记(上)

## 说明

+ 强类型语言TS，实际上一直在用，虽然用的磕磕绊绊，不过这次因为是钱包，感觉类型等安全问题要重视，再学习一遍。
+ 学习Up主的一个学习视频，https://www.bilibili.com/video/BV1XG411E7dg/?p=5&spm_id_from=pageDriver&vd_source=0a978d5cb963890b0cab49f66fae30af，他学习的是ORelly的一本书。

+ 《Learning TypeScript Enhance Your Web Development Skills Using Type-Safe JavaScript》，Josh Goldberg，O' Reilly，2022

+ 书的源代码：https://www.learningtypescript.com/from-javascript-to-typescript/the-typeinator/
+ 以下是笔记，计划后面有点经验和体感了，给不熟悉前端的后端同学开个快速入门课程。

## 1 JS to TS

类型系统提示代码安全和稳定性

类型检查器分析代码

仅在开发环境下的类型注释：何时添加

TS同IDE结合重构代码

TS内建推断和新语法来表示复杂类型

95年Brendan Eich，10天设计了JS

Show me the best language, I'll show you the no users language---Anders Hejlsberg (TS createor)

### JS问题

类型没有上下文，容易包含编译报错陷阱，没有强类型检查

JsDoc文档对代码无约束切重构要重写，结构松散

配套工具较弱



### TS优势

2010年创建，12年开源，Anders Hejlsberg 创建（大牛）

四部分：编程语言（JS+特殊类型语法）、类型检查器、编译器（报告问题+输出等价JS）、语言服务（协调编译器工作）

### 开始

```
node --version
npm install --location=global typescript (failed with error)

 npm i -g typescript (it is ok!)
 npm install -g typescript(the same)
 
tsc --version

具体项目目录
tsc --init
生成tsconfig.json

编译：tsc index.ts
```

其他：

```
npx http-server 可以一句话帮你开启一个静态服务器！
npx 会自动查找当前依赖包中的可执行文件，如果找不到，就会去 PATH 里找。如果依然找不到，就会帮你安装！
```



### 其他

TS不会和JS冲突

TS编译为JS需要时间，Babel可以更快编译？



## 2 类型系统

### TS基础类型7种

```
null
undefined
boolean
string
number
bigint
symbol
```

类型系统：代码中数据类型规则的体系，成为类型系统

### TS类型系统：

理解代码所有值的类型

观察初始值，推断可能包含什么类型

观察每个值，在代码中的所有用法

如果值和类型不匹配，报错提示

---

返回ts代码两种错误类型：

语法错误，写错了，词不达意

类型错误，方法或者值与类型不匹配（**可赋值性** assignability+方法属性）

如果没有那些属性，则ts会报错，成为**类型形状** type shapes

类型推断不匹配的报错，可以显式说明变量类型，成为**类型注释**

```
let body_weight: number = 49; 
使用类型注释解决类型推断不符合预期的问题
避免变量没有初始值和类型，因为会被隐式推断为any类型，是风险

let birthplace: string;
birthplace = true;  (会报错提示)
```

无法推断类型的根据赋值自动识别为对应类型，也具备了对应类型的方法

---

约定

同目录下多个ts文件

包含import或者export语句，则该文件是一个模块 module

不包含上述的，就是普通ts代码，是script



变量名冲突

1.模块内变量名的冲突，根据是否有import及import的变量名（别名）是否和本模块相同，相同则冲突。

2.不同脚本中存在同名变量，会产生冲突，可tsconfig中配置更改冲突规则。



## 3 联合和字面量？

### Unions: 允许两个多多个类型的赋值

```
let mathematician = Math.random()>0.5 ? "祖冲之" : undefined;
let thinker: string|null;
这种变量因为可能是两种类型，则访问该变量方法，会排除单个类型的方法，保留共有属性方法
```



narrowing：将值允许的类型减少为不是一个或多个可能的类型？

```
类型守卫代码<type guard>，指示ts此处（联合类型）的具体类型，就可以访问该类型独有的属性，这个过程叫narrowing.
有几种方式收缩：
给一个确定类型的初始值
对变量的具体值进行判断
对变量值的类型进行判断

```

### 字面量 literals

一般是指const固定的类型，固定了变量类型和值，具有对应类型的属性方法

```
这个是联合类型混合了字面量类型的变量：
let score: number|"不及格"|"及格"|"良好"|"优秀";
```

虚值 

falsy: false 0 -0 0n "" null undefined NaN

真值

不等于虚值的就是真值



严格空值检查

Tsconfig.json中的 strictNullChecks = true ，则null和undefined不能赋值给其他类型



### 类型别名

type关键字可以给某类型一个别名，如果它很长的话，常用大驼峰命名，可在任意处定义，存在即可。

```
type RawData = boolean | number | string |null |undefined;

let raw_data_first: RawData = false;

type IDMabe = Id | undefined | null;
type Id = number | string;
```



## 4 对象

### 对象字面量

对象类型匹配对应的属性和属性类型，缺少/增加属性，属性类型不匹配，都会产生类型错误。

变量/属性+？表示可选

```
let singer: {
name: string,
age?: number,
work_title: string|undefined,
} = {
name: "张纪中",
// age属性可以不存在
work_title: "导演" // work_title 必须存在，可以是undefined。
};
```

1.使用字面变量赋值时，必须严格满足对象类型要求，不能有额外属性。

2.而将对象赋值给变量，则只要求该对象包含变量类型要求的属性，对象有额外属性不会报错。

```
type WithFirstName = {first_name: string;};
type WithLastName = {last_name: string;};

const has_both = {
first_name: "Lucas",
last_name: "Clif"
};

let with_first_name: WithFirstName = has_both;
变量被赋值一个对象，只要对象有变量类型的一个属性，则可以赋值

const has_both_error: WithFirstName = {
	first_name: "Lucas",
	last_name: "Clif"  //这里编译器会报错，因为变量被赋值的是字面量，则要求属性完全匹配
};

ps：错误的代码编译为js后，可以正常输出，js的容忍度很高啊 
```

推断的联合对象类型

不是共有属性的，在单个类型中以可选属性表示,属性类型是undefined

```
let best_song = Math.random()>0.5 ? {
	name: "song1",
	single: true,
	duration: "4:08"
} : {
	name:"song2",
	single: "是",
	release: new Date(2022, 0, 20)
};
```

显式的联合对象类型，非共有属性，不可直接访问，需要narrowing。

```
if in判断属性是否存在
或者增加判断属性来访问判断
```



intersection类型

更像是满足多个类型的类型，而不是满足多个类型的共有部分？

```
let one_written_art: Artwork & Writing = {
	enre: "genre",
	pages: 100,
	name: "name"
}

type Artwork = {
	genre: string;
	name: string;
};

type Writing ={
	pages: number;
	name: string;
};
```

多个类型interset时，某个属性名相同单类型不同时，这个属性就是never类型

never类型表示不可能出现在代码的类型状态。 



## 5 函数

### 函数参数

parameter，定义的参数，形式参数，行参

argument，传递的参数，实际参数，实参

形参：type

形参？: type，表示可选参数，可以有多个

形参 = default value，表示默认参数，不用标记类型，自动根据值判断类型

其余参数定义： function sing_all_songs(singer: string, ...songs: string[]) {}

实参直接值，类型与形参类型相同，数量也相同

------

作为返回类型: ts根据返回值判断返回类型

1.多个return语句(活return结果可能不同)的函数，返回值类型是每个return结果的联合类型

2.显式返回值类型： 用箭头函数，后面是返回值类型 function aaa(aa: string, bb=0): number {}

 显式返回可以提升类型检查速度，不必推断，或有必要强制返回值类型时

------

函数类型：写法和箭头函数类似，知识返回值变为返回值类型，function aaa(v: string) => string {}

void函数返回值类型表示返回nothing，undefined不匹配void类型

never返回值类型表示never return，用于总抛出错误或者在无限循环结构的函数。

-----

函数重载 overload

因为传入参数形式不同而执行不同动作，即函数有多个版本，成为函数重载。

1.先重载签名，多次声明函数，函数名相同，参数形式和返回值不同，不写函数体

2.然后实现重载签名，完成函数体内容，记住要兼容所有重载签名才可以

重载是最后手段，ts编译为js后，重载签名会被清除

----

## 6 数组

数组和元组（array和tuple）

type [] or [values],依靠值判断类型

let variance: number[];

Variance = [41, -7, 0, 6.4];

联合数组类型：type array_of_string_or_numbers =  (string | number)[];

避免any类型的数组，例如let aa = []；数组类型会不断根据值而变化

```
let three_demesional : boolean[][][][];
let complex: ((Symbo[] | bigint[])[])[];
```

TS的默认数组类型认为访问数组元素时一定能访问到，及时超出index，有开关可改变。

扩展运算：合并数组，不同类型将产生联合类型

剩余参数类型：...

```
const aa = [pp"","rr","ss"];
const bb = [4,5,8,9];
const joined_array = [...aa, ...bb];

function bubble(message: string, ...stuff: string[]){}

let abc: [string, number]; //注意：这个是元组，不是数组，数组是 let abc: (string | number)[];
```

优先判断为数组类型而非元组

显式注释元组类型: let m_tuple: [bigint, number] = [0n, 0];

在数字字面量后写上常量断言(as const): const readonly_tuple = [0n, 0] as const;



## 7 接口

做自己的类型，超越内置类型

接口：描述对象的形状，和对象类型的别名很类似。

1.提供易于阅读的错误信息

2.很快的编译速度

3.与类的交互性很好

```
//类型别名
type Book = {
title: string;
pages: number;
};

//等效的接口
interface IBook {
	title: string;
	pages: number;
};

let loved_book: IBook = {
	title: "Master C#",
	pages: 705
}
```

接口和类型别名区别：

1.接口可以合并（相同名字的接口）以增强功能，在协同第三方代码，如npm包很有用

2.接口可以描述类（class）的结构，类型别名不行

3.接口容易在checker内缓存，有更快的类型检查速度，类型别名本质是粘贴了对象类型的字面量。

4.接口被视为命名的对象，而不是一个未命名的字面量的别名，有更易阅读的错误信息

### 接口属性类型

attribute?:，表示可选属性

Readonly attribute, 表示只读属性（编译为JS此属性会失效）

### 接口函数和方法

方法语法：Member(): void;

属性语法：Member: () => void;

staff.member(); // 以上两种定义这里都正确

方法与函都支持可选修饰符，方法与函数大多数情况下是等同的，可交换。

二者区别：

1.方法不可以被readonly修饰

2.接口合并时，对方法和函数的处理不同

3.一些类型操作，对待方法和函数不同

如果你知道函数可能使用this，那用方法语法，否则可以使用属性语法

### 调用签名

一个值可以像函数一样调用，若函数有特殊的内部属性，可以调用签名来描述

没太看懂必要性？

### 索引签名

定义接口太多了就可以索引

你想存储键值时，key是位置的，用Map更安全，因为包含了undefined联合类型

key的类型只能是string, number, symbol

### 接口嵌套

```
interface Novel {
	author: {
		name: string;
		age?: number;
	},
	setting: Setting;
}
interface Setting {	
	place: string;
	readonly year: number;
}

let hovel: Novel = {
	author: {
		name: "jjj"
	},
	setting: {
		place: "England",
		year: 1199
	}
};
```

### 接口扩展（派生）

一个接口可以扩展另外一个接口，派生接口复制基础接口所有成员，还可以添加自己的新成员，让代码复用。

```
interface Writing {
	title: string;
	review?:(() => void) | boolean[];
	publish?(): symbol;
}

interface Novella extends Writing {
	pages: number; //新增属性
	review: boolean[]; //覆盖属性，保障类型匹配
	pulibsh?: () => bigint; //复制属性，类型不对
}

扩展多个属性

interface Avert extends Revert, Invert {
	...
}
```

### 合并接口

合并的接口要同名，同一作用域。

一般为了增强功能，比如自定义一个接口来合并内置的全局接口，比如Window，或者外部包的接口

合并接口的属性类型必须一模一样



## 8 类

TS对待方法和构造器就像对待函数，允许声明参数类型、可选参数、参数默认值、剩余参数和函数返回值。

类的属性必须显式声明，不会从构造器的赋值来反推有哪些属性。

两种语法，让类中成员能像函数一样调用：

1.方法语法，创建的函数叫方法，不同实例访问的方法都是同一个函数。

2.属性语法，创建一个函数赋值给属性，不同实例有自己的属性，属性语法创建的函数是相互独立的。

### 初始化检查

TS默认开启严格的属性初始化检查：

1.属性类型不包含undefined

2.属性值没有初始值

3.没有在构造器为此属性复制

那TS会抱怨这个属性的不确定性，当然，属性名后面加!, attribute!: string;

就跳过了严格检查，表明第一次使用前一定会赋值。

可选属性在初始化检查中会跳过

只读属性指南在声明初始化或者构造器中初始化，不能在类方法、属性或者实例中初始化 



### 类作为类型

一个对象结构满足类的要求，及时该对象不是类的实例，该对象也能匹配类（就是类的类型）

因为TS结构化检查只关心形状，不关心对象如何声明。

### 类与接口

类能够实现接口，让类实例遵循接口的类型要求

类实现接口，不改变类的使用，不会复制接口的任何成员到类中？？，TS不会跟军接口来推断类成员类型

接口只是起到纯净的安全性检查作用，允许类实现多个接口，不冲突即可

当接口被设计去让类实现时，**接口的函数**应该使用**方法语法定**义

不能同时满足两个接口的要求，智能实现其中一个

### 类的扩展

类能像接口那样扩展，子类拥有所有基类的属性和方法

子类没有写自己的构造器，则会隐式调用积累构造器。

子类如果有自己构造器，则必须通过super()调用基类构造器。

子类必须调用基类构造器，才可以访问this，super。最好把super()写在构造器的顶层语句。（前后为鸡和蛋了。。。）

### 覆盖方法/属性

方法可覆盖，单参数类型要匹配，返回值类型要匹配，匹配指兼容，非复制

属性同样，可覆盖，保持类型匹配，兼容

### 抽象类

抽象类是一个基类，不说明实现，值描述结构，其他类扩展抽象类后，可以实例化，抽象类不可实例化。

类名或方法前加 abstact即可

### 成员可见性

Public 默认的，允许任何人，任何地点访问

Protected, 允许在类本身和其子类访问

Private，允许类本身访问

#，允许在类本身访问？？有啥区别？#是JS原版的，其他关键字是TS加的，#是原始私有成员的标志

#不能与其他TS可见性关键字混用，可以和readonly，static混用；readonly与可见性关键字可以混用

注意static静态属性访问需要类名而不是this来访问

实例不能访问protected和private

注意static修饰符，表示该字段/成员只能被类本身（不是在类本身中）访问，例如类名.static变量

顺序：可见性关键字  static  readonly
