# Part1-TypeScript

## 一、TypeScript简介

TypeScript（下称TS） 是 JavaScript 的超集，TS其实就是类型化的 JavaScript（下称JS），它不仅支持 JS的所有特性，还在 JS的基础上添加了静态类型注解扩展。

## 二、TypeScript环境搭建

首先，确定Node版本，这里强调一下，本课程所有的项目均基于Node v14.17.0进行开发：

```shell
# 检查出版本没有14.17.0
$ nvm list

  * 12.10.0 (Currently using 64-bit executable)

# 安装14.17.0并检查
$ nvm install 14.17.0
$ nvm use 14.17.0
$ nvm list

  * 14.17.0 (Currently using 64-bit executable)
    12.10.0
```

检查得到Node版本为14.17.0即可。

全局安装TS

```shell
$ npm i typescript -g
# 或者使用yarn
$ yarn add global typescript
```

安装完成后检查TS版本：

```shell
$ tsc --version
```

如果可以出来一个版本号，就代表已经全局安装成功。

### 1、创建项目与文件转换

创建好一个空白项目（如：ts-study）后，用vscode打开，创建一个名为 `demo1.ts` 的文件：

```typescript
function fn(){
    let str: string = "你好世界";
    console.log(str);
}

fn();
```

然后打开终端，使用 `node demo1.ts` 运行该ts文件，会发现报错：

[![image-20211205142555467](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxxeq8gj30ji06o0t7.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxxeq8gj30ji06o0t7.jpg)

原因是ts需要转为js文件才能被识别。

因此，先使用 `tsc demo1.ts` 将文件进行转换，转换成功会看到当前目录下多了一个demo1.js，此时使用node去运行这份js文件，才能看到结果：

[![image-20211205142816104](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxqh8l1j30xh0migmx.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxqh8l1j30xh0migmx.jpg)

### 2、自动转换

如果每次都要先将ts文件转一次js文件，再运行js文件，那么过程太过繁琐，可以直接使用插件帮我们解决：

```shell
# 全局安装ts-node
$ npm i -g ts-node@8.5.4
```

安装成功后，删掉项目中的 `demo1.js`，然后直接在终端运行：

```shell
$ ts-node demo1.ts
```

如果可以直接得到运行结果，如下图，即代表运行成功。

[![image-20211205143255479](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxuvnvyj30iq01xjr8.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxuvnvyj30iq01xjr8.jpg)

## 三、简单数据类型

### 1、简单数据类型（掌握）

同JS一样，TS也有简单数据类型，而且大同小异。拿上述使用的代码：

```typescript
function fn(){
    let str: string = "你好世界";
    console.log(str);
}

fn();
```

这里 `str:string` 中，str是变量名称，`:string` 是指定这个字段的类型为字符串，如果你给它赋值任何其他数据类型，都是不被允许的：

[![image-20211205144103199](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxz1jbwj30jy07b74o.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxz1jbwj30jy07b74o.jpg)

由此，我们了解了TS相较于JS，更加注重变量的数据类型校验。剩余的TS简单数据类型还包括：

* 字符串string
* 数字number / bigint(一般较大的整数才会使用)
* 布尔值boolean
* 唯一值symbol

> 请注意：虽然number和bigint都表示数字，但是这两个类型不兼容。

### 2、静态类型检测（了解）

什么是静态类型呢？所谓静态类型，就是一个变量如果定义了它的类型，那么这个类型就不再允许修改。在编译时期，静态类型的编程语言即可准确地发现类型错误，这就是静态类型检测的优势。

来观看下面这段代码：

```typescript
let str: string = "你好世界";
str = 123;	// 错误的修改
```

在编译（转译）时期，TypeScript 编译器将通过对比检测变量接收值的类型与我们显示注解的类型，从而检测类型是否存在错误。如果两个类型完全一致，显示检测通过；如果两个类型不一致，它就会抛出一个编译期错误，告知我们编码错误，效果如下图所示。

[![image-20211205145301302](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxwlnmxj30im072t92.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxwlnmxj30im072t92.jpg)

这个静态类型检测在VsCode中已经完美集成，开发者只需要看提示即可了解需要修改的地方。

### 3、类型注释与推断（掌握）

当写完以下代码

```typescript
function fn(one, two){
    console.log(one + two);
}

let result = fn(1, 2);
```

然后鼠标移上形参one时，会得到一个提示。

[![image-20211205152650871](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxsp18sj30s705sgm1.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxsp18sj30s705sgm1.jpg)

当提示为any时，代表你需要给它写上类型：

```typescript
function fn(one: number, two: number){
    return one + two;
}

let result = fn(1, 2);
```

你加上的这两个number，就是**类型注释**。而此时，当你鼠标移上result时，就会得到一个新的提示：

[![image-20211205152510159](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxriakxj30ex05oaa8.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxriakxj30ex05oaa8.jpg)

这个提示告诉你result是个number，但我们并没有声明result是number呀！这就是**类型推断**。

## 四、复杂数据类型

同JS一样，TS也有复杂数据类型，包含以下几种：

* 数组类型Array
* 元组类型Tuple
* 特殊类型any、unknown、void、undefined、null、never、object

接下来将围绕这几种数据类型进行讲解。

### 1、数组类型Array（掌握）

一个数组的每一项其实是可以定义它的字段类型的：

```typescript
let arr1: number[] = [1,2,3]
let arr2: string[] = ["1", "2", "3"]
```

如果没有按规定赋值，就会报错：

[![image-20211205150101292](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxvs4eyj30na0350sz.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxvs4eyj30na0350sz.jpg)

以上代码也可以用这种方式表示：

```typescript
let arr1: Array<Number> = [1,2,3]
let arr2: Array<String> = ["1", "2", "3"]
```

> 注意：
>
> 建议优先使用第一种方式书写，否则在React的JSX语法中可能会冲突。下文中所有可以使用这类写法的，都不会讲。

当然，所有数组的操作也必须符合以上的数据类型定义：

```typescript
arr2.push("4");     // 正确
arr2.push(4);       // 错误
```

### 2、类型推断（掌握）

如果每个数组项都是不同的值，TS会自动给我们提示：

[![image-20211205151558357](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxtev2dj30gb034gln.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxtev2dj30gb034gln.jpg)

这就是类型推断。TS帮我们推断出这个数组共有多少种数据类型，因此你可以修改代码为：

```typescript
let arr: (string | number | boolean)[] = ["张三", true, 16];
```

如此，开发者便可以较为智能的书写代码。

### 3、元组类型Tuple（了解）

如果数组中的每一项也是个数组，应该怎么写呢？这时候就需要依赖元组Tuple。

元组最重要的特性是可以限制数组元素的个数和类型，它特别适合用来实现多值返回。在 JavaScript 中并没有元组的概念，作为一门动态类型语言，它的优势是天然支持多类型元素数组。

```typescript
let arr: [string, number][] = [
    ["张三", 13],
    ["李四", 14],
    ["王五", 15]
];
```

如果每个数组项不是数组，而是对象，那么写法如下：

```typescript
let arr: {name: string, age: number}[] = [
    {name: "刘备", age: 51},
    {name: "关羽", age: 51},
    {name: "张飞", age: 51}
]
```

### 4、任意类型any（掌握）

any 指的是一个任意类型，它是官方提供的绕过静态类型检测的方法。如：

```typescript
let arr: any[] = [11, "你好世界"]
```

> 注意：
>
> 非必要不使用any，这几乎违背了TS的出发点。

### 5、不确定变量unknown（了解）

unknown 是 TypeScript 3.0 中添加的一个类型，它主要用来描述类型并不确定的变量。在 3.0 以前的版本中，只有使用 any 才能满足这种动态类型场景。与 any 不同的是，unknown 在类型上更安全。比如我们可以将任意类型的值赋值给 unknown，但 unknown 类型的值只能赋值给 unknown 或 any。

```typescript
let str: unknown;
let num: string = str;  // 不能将类型“unknown”分配给类型“string”。
let abc: any = str;     // 没有报错
```

### 6、无返回值void（掌握）

当一个函数没有返回值时，需要加上void：

```typescript
function fn(one: number, two: number): void{
    console.log(123)
}

fn(1,2);
```

### 7、undefined与null（了解）

这两个没什么好讲的，此处省略。

### 8、never（了解）

never 表示永远不会发生值的类型。一个永远不会有返回值，或者产生死循环的函数，就可以加上never：

```typescript
// 只有产生报错才会产生错误返回
function fn1(str: string): never {
    throw Error(str);
}

// 只有条件为真才会产生值返回
function fn2(): never {
    while(true) {}
}
```

### 9、object（了解）

object 类型表示非原始类型的类型，即非 number、string、boolean、bigint、symbol、null、undefined 的类型。

### 10、类型断言（了解）

如果一段js代码，你已经可以很明确的知道返回值一定是某种数据类型，就可以使用as给它断言：

```typescript
const arr: number[] = [1,2,3,4]
const result: number = arr.find(num=>num>2) as number;
```

## 五、字面量类型（了解）

在 TypeScript 中，字面量不仅可以表示值，还可以表示类型，即所谓的字面量类型。

使用值所代表的数据类型来作为变量的数据类型，简单来说，即：**值也可以作为类型使用**。但要注意，目前只能是string、number和boolean三种数据类型可以写成字面量类型。

```typescript
const str: "你好世界" = "你好世界";
const num: 123 = 123;
const bool: true = true;
```

## 六、返回值类型（掌握）

在上文讲述void的时候，其实已经使用过了，大致如下：

```typescript
function fn(one: number, two: number): number{
    return one+two;
}

fn(1,2);
```

## 七、剩余参数（了解）

在 ES6 中，JavaScript 支持函数参数的剩余参数，它可以把多个参数收集到一个变量中。同样，在TypeScript 中也支持这样的参数类型定义。

```typescript
// 参数个数不确定，但要实现累加
function fn(num1: number, ...nums: number[]): number{
    return nums.reduce((prev,next)=>prev+next, num1);
}

console.log( fn(1,2) );
console.log( fn(1,2,3) );
```

效果如图：

[![image-20211205160442935](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxuav5rj30jq02ndfo.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxuav5rj30jq02ndfo.jpg)

## 八、this（了解）

原生JS中的this指向非常模糊，函数中的this只有当函数被调用了才能知道this指向谁。而TS中，严格模式下必须显式地指定this指向，否则如下图所示：

[![image-20211205161843410](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfy0t1juj30s60c0wf4.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfy0t1juj30s60c0wf4.jpg)

因此代码应该改为：

```typescript
function fn(this: Window, str: string){		// 这里Window是大写
    console.log(this);
}
window.fn = fn;			// 这里window是小写
window.fn("你好世界");
```

这里要注意：

1、这段代码不会成功执行，因为你是在ts-node环境下运行，不存在window，也不会有global；

2、这里的this是伪形参，不会被编译为js。

## 九、ES6面向对象

ES5的面向对象是由函数实现，加上TS的话按照上述内容即可。而ES6的面向对象是使用class来定义类，因此需要特殊讲一下：

### 1、类（掌握）

以下描述一个Animal类：

```typescript
class Animal {
    name: string;   // 声明了Animal类中的name属性必须为string类型
    constructor(name: string){
        this.name = name;
    }
    sayName():void{	// 加void表示没有返回值
        console.log(this.name);
    }
}

let animal = new Animal("嘟嘟");
animal.sayName();   // 嘟嘟
```

### 2、继承（掌握）

定义一个Cat类，继承自Animal类：

```typescript
class Animal {
    name: string;   // 声明了Animal类中的name属性必须为string类型
    constructor(name: string){
        this.name = name;
    }
    sayName():void{
        console.log(this.name);
    }
}

class Cat extends Animal {
    constructor(name: string){
        super(name);
    }
    shout(): void{
        console.log(`${this.name}正在喊“喵喵”`);
    }
}

let cat = new Cat("嘟嘟");
cat.shout();   // 嘟嘟正在喊“喵喵”
```

### 3、公共、私有与受保护的修饰符（了解）

类属性和方法除了可以通过 extends 被继承之外，还可以通过修饰符控制可访问性。

在 TypeScript 中就支持 3 种访问修饰符，分别是 public、private、protected。

#### a. 公共修饰符public

public 修饰的是在任何地方可见、公有的属性或方法。上述代码中，凡是没有加修饰符的，都是默认已经自带public：

```typescript
class Animal {
    name: string;
    constructor(name: string){
        this.name = name;
    }
    sayName():void{
        console.log(this.name);
    }
}

let animal = new Animal("张三");
animal.sayName();

// 以上代码相当于：

class Animal {
    public name: string;
    constructor(name: string){
        this.name = name;
    }
    public sayName():void{
        console.log(this.name);
    }
}

let animal = new Animal("张三");
animal.sayName();
```

#### b. 私有修饰符private

private 修饰的是仅在当前类中可见、私有的属性或方法。具体如下：

```typescript
class Animal {
    public name: string;
    private age: number;	// 只能在当前类中使用，实例和子类都不能用
    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }
    public sayName():void{
        console.log(`${this.name}今年${this.age}岁`);
    }
}

let animal = new Animal("张三", 11);
animal.sayName();           // 张三今年11岁
console.log(animal.age);    // 属性“age”为私有属性，只能在类“Animal”中访问 ✖
```

#### c. 受保护修饰符protected

protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法。

```typescript
class Animal {
    public name: string;
    protected age: number;  // 只能在当前类和子类中使用，实例不能用
    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }
}

let animal = new Animal("张三", 11);
console.log(animal.age);    // 属性“age”受保护，只能在类“Animal”及其子类中访问 ✖

class Cat extends Animal {
    constructor(name: string, age: number){
        super(name, age);
    }
    shout(): void{
        console.log(`${this.name}正在喊“喵喵”，它今年${this.age}岁`);	 // 可以使用 ✔
    }
}

let cat = new Cat("嘟嘟", 11);
cat.shout();   // 嘟嘟
```

### 4、只读属性与静态属性（了解）

#### a. 只读属性readonly

加上readonly的属性只能读不能写：

[![image-20211205165223243](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfy1v5e4j30qv08rjs5.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfy1v5e4j30qv08rjs5.jpg)

#### b. 静态属性static

加上static的属性与方法，无需实例化即可调用：

```typescript
class Animal {
    // 直接给静态属性赋值
    static username: string = "张三";
}

Animal.username; // 张三
```

### 5、抽象类abstract（了解）

简单一句话：抽象类无法被实例化。

```typescript
// 定义一个抽象类
abstract class Animal {
    name: string;
    constructor(name:string){
        this.name = name;
    }
}

let animal = new Animal("张三");    // 无法创建抽象类的实例。
```

## 十、接口类型

请先看这段js代码：

```js
function fn({name, age}){
    console.log(`${name}的年龄为：${age}`)
}

fn({name: "张三", age: 30});	// 张三的年龄为：30
```

它的作用是给函数传了个对象，并且把对象中两个字段解构出来使用。但如果你要指定两个字段的类型，就会报错了：

[![image-20211205171331006](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxygzlbj30le06aaaf.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfxygzlbj30le06aaaf.jpg)

此时如果你通过以下代码书写，效果就不一样了：

```typescript
function fn({name, age}: {name: string; age: number}){
    console.log(`${name}的年龄为：${age}`)
}

fn({name: "张三", age: 30});	// 张三的年龄为：30
```

### 1、接口类型

其实这段代码是接口类型的演变，原来的表现形式应该如此：

```typescript
// 使用接口定义字段类型
// 接口一般首字母大写，经常使用I开头
interface IObj {
    name: string;
    age: number;
}

function fn(obj: IObj): void{
    console.log(`${obj.name}今年${obj.age}岁`)
}

fn({name: "张三", age: 33});	// 张三今年33岁
```

以上接口中，name和age的顺序是可以打乱的，不会影响代码执行。

### 2、可缺省属性

接口中定义的字段，参数必须传，但有些参数我们希望可传可不传，可以如下：

```typescript
interface IObj {
    name: string;
    age?: number;	// ?表示该参数可传可不传
}

function fn(obj: IObj): void{
    console.log(`${obj.name}今年${obj.age || 0}岁`)
}

fn({name: "张三"})	// 张三今年0岁
```

### 3、任意属性

其实属性名称有时也不确定，因此可以使用以下方式定义，但值必须为any。

```typescript
interface IObj {
    name: string;
    // [propName: string]: string | number;
    // 这个参数名称可能也是不确定的，也不清楚它的类型，可以any，也可指定若干数据类型，建议直接any
    [propName: string]: any; 
}

function fn(obj: IObj): void{
    console.log(`${obj.name}是${obj.sex || '男'}性`)
    
}

fn({name: "张三"})				// 张三是男性
fn({name: "张三", sex: "女"})	   // 张三是女性
```

> 注意：
>
> **任意属性和可选属性最好不能共存**，即便共存最好类型一致。

## 十一、泛型

泛型是指在定义函数、接口，或者类的时候，不预先指定具体的类型，而是在使用的时候在指定类型的一种特性。

现在有一个需求：有一个函数可以创建指定长度的数组，每一项都要填充一个指定值。

```typescript
function createArr(length: number, value: any): Array<any>{
// 或：function createArr(length: number, value: any): any[]{
    let arr = [];
    for(var i=0;i<length;i++){
        arr[i] = value;
    }
    return arr;
}

console.log( createArr(3, "猴子的救兵") );  // [ '猴子的救兵', '猴子的救兵', '猴子的救兵' ]
```

以上代码在函数调用前，是不清楚返回的数据类型的，因为value的数据类型为any。但我们说尽量不使用any，毕竟它绕过了类型检查，因此，我们需要借助泛型来解决。

### 1、泛型简单使用

一般我们用 `T` 来代表输入和返回的数据类型，直到调用了才明确 `T` 是什么数据类型：

```typescript
function createArr<T>(length: number, value: T): Array<T>{
// 或：function createArr<T>(length: number, value: T): T[]{
    let arr: T[] = [];
    for(var i=0;i<length;i++){
        arr[i] = value;
    }
    return arr;
}

console.log( createArr<string>(3, "猴子的救兵") );  // [ '猴子的救兵', '猴子的救兵', '猴子的救兵' ]
```

### 2、多个类型参数

如果有多个参数都是类型未知的，那么泛型应该这么写：

```typescript
// 情况一：多个参数传入
function fn<U, T>(flag: U, value: T): Array<U | T>{
    let arr: Array<U | T> = [flag, value];
    return arr;
}

console.log(fn(true, "我是孙悟空"))		// [ true, '我是孙悟空' ]

// 情况二：单个参数传入，且为数组
function fn<U, T>(arr: [U, T]): Array<U | T>{
    let newArr: Array<U | T> = [...arr];
    return newArr;
}

console.log(fn([true, "我是孙悟空"]))  // [ true, '我是孙悟空' ]
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gb.akanote.cn/cms/part1-typescript.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
