Skip to content

TypeScript

一、快速入门

1.1 TypeScript简介

  1. TypeScript是JavaScript的超集
  2. 它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。
  3. TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
  4. TS完全兼容JS,换言之,任何的JS代码都可以直接当成TS使用。
  5. 相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。

1.2 TypeScript 开发环境搭建

  1. 下载安装Node.js

  2. 使用npm全局安装typescript

    npm i -g typescript

  3. 创建一个ts文件

  4. 使用tsc对ts文件进行编译

    在ts文件所在目录 执行命令:tsc xxx.ts

二、基本类型

2.1 类型声明

  • 类型声明是TS非常重要的一个特点

  • 通过类型声明可以指定TS中变量(形参,返回值)的类型

  • 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错

  • 类型声明给变量设置了类型,使得变量只能存储某种类型的值

typescript
// 在JS中是不限制变量的类型的
// 先声明再赋值
let a: string; // 变量a只能存储字符串类型
let b: number; // 变量b只能存储数值
let c: boolean; // 变量c只能存储布尔值

a = "hello";
// a = 100 //警告:不能将类型“number”分配给类型“string”

b = 666;
// b = "aa" //警告:不能将类型“string”分配给类型“number”

c = true;
// c = 12 //警告:不能将类型“number”分配给类型“boolean”

// JS 中的函数是不考虑参数个数和类型的
// 为函数的参数,和返回值 指定类型
function demo(x: number, y: number): number {
  return x + y;
}

demo(1, 2);
// demo(1,2,3) // 警告:应有 2 个参数,但获得 3 个
// demo("13")// 警告:应有 2 个参数,但获得 1 个

注意:

  1. 类型名为小写

  2. 当为变量声明了类型后以后再对变量进行赋值时,若类型不符就会报错

2.2 类型推断

在声明变量并赋值时不必声明变量类型,TypeScript会推断出变量的类型

TypeScript
// 变量声明时赋值(常用)
let d = -99; //TypeScript会推断出变量d的类型是数字
// let d: number = -99; // 等同于上一行代码
// d = false;//警告:不能将类型“boolean”分配给类型“number”

2.3 类型总览

2.3.1 JavaScript 中的数据类型:

stringnumberbooleannullundefinedbigintsymbolobject

备注:其中 object 包含: Array 、 Function 、 Date ......

2.3.2 TypeScript 中的数据类型:
  1. 以上所有
  2. 六个新类型: voidneverunknownanyenumtuple
  3. 自定义类型: typeinterface

注意点: JS 中的这三个构造函数: Number 、 String 、 Boolean ,他们只用于包装对象,正常开发时,很少去他们,在 TS 中也是同理。

2.3.3 类型总览:
类型例子描述
number1, -33, 2.5任意数字
string'hi', "hi", hi任意字符串
booleantrue、false布尔值true或false
字面量其本身限制变量的值就是该字面量的值
any*任意类型
unknown*类型安全的any
void空值(undefined)没有值(或undefined)
never没有值不能是任何值
object任意的JS对象
tuple[4,5]元组,TS新增类型,固定长度数组
enum"enum{A, B}"枚举,TS中新增类型

2.4 常用类型

2.4.1 字面量:

也可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围

typescript
let a: "你好"; // a的值只能为字符串"你好"
let b: 100; // b的值只能是数字100

// a = "1"  //警告:不能将类型"1"分配给类型"你好"
// b = 200; //警告:不能将类型“200”分配给类型“100”

let gender: "男" | "女"; //定义⼀个gender变量,值只能为字符串“男”或“⼥”
gender = "男";
// gender = "未知"; //不能将类型“"未知"”分配给类型“"男" | "⼥"”

let c: number | string; //定义一个c变量,值可以是数字或字符串
// |:或  联合类型:number | string
c = 100;
c = "100";
2.4.2 any

any 的含义是:任意类型,一旦将变量类型限制为any,那么就意味着放弃了对该变量的类型检查。

typescript
// 明确的表示a的类型时any --- 显示的any
let a: any;
// 以下对a的赋值,均⽆警告
a = 1;
a = "1";
a = true;

// 声明变量时不指定类型,则TS解析器会自动判断类型为any --- 隐式any
let b;
//以下对b的赋值,均⽆警告
b = 1;
b = "2";
b = true;

// 注意:any类型的变量,可以赋值给任意类型的变量(在开发中一般不要使用 )
let e;
e = 1;
let f: string;
f = e; // 无警告 但会影响s的类型检测,导致运行时出错
// 此时f的值与f的类型不同,发生错误,但不会出现警告

注意:any 类型的变量,可以赋值给任意类型的变量(影响被赋值变量的类型检查,导致出现其值与类型 不匹配的错误)

2.4.3 unknown

unknown 的含义是:未知类型,随着变量的动态赋值变量的类型也会随之改变。

unknown 可以理解为一个类型安全的 any (不能直接赋值给其他变量)

unknown 适用于:开始不知道数据的具体类型,后期才能确定数据的类型

TypeScript
// 设置a的类型为unknown
let a: unknown;

// /以下对a的赋值,均正常
a = 1;
console.log(typeof a); // number
a = "2";
console.log(typeof a); // string
a = false;
console.log(typeof a); // boolean
// 随着对a的动态赋值a的类型也会随之改变

// 设置x的数据类型为string
let x = "abc";
// x = a //警告:不能将类型“unknown”分配给类型“string”

// 若想将 a 赋值给 x 有如下三种写法:
// 第一种方法:加类型判断,当a的类型为string时,才赋值给x,避免出现类型错误
if (typeof a === "string") {
  x = a;
}

// 第二种方式:类型断言 告诉解析器变量的实际类型
x = a as string; 

// 第三种方式:类型断言 告诉解析器变量的实际类型
x = <string>a;

any 与 unknown 的区别:

  1. any可以赋值给任意类型的变量而unknown不可以
  2. any 后点任何的东西都不会报错,而unknown 正好与之相反。
TypeScript
let str1: string = 'hello'
str1.toUpperCase() //⽆警告
let str2: any = 'hello'
str2.toUpperCase() //⽆警告
let str3: unknown = 'hello';
str3.toUpperCase() //警告:“str3”的类型为“未知”

// 使⽤断⾔强制指定str3的类型为string
(str3 as string).toUpperCase() //⽆警告
2.4.4 never

never 的含义是:任何值都不是,简而言之就是不能有值,undefinednull''0 都不行!

  1. 几乎不用 never 去直接限制变量,因为没有意义
TypeScript
// 指定a的类型为never,那就意味着a以后不存在任何的数据了
let a: never;

// 以下对a的赋值都会有警告
// a = 1
// a = true
// a = undefined
// a = null
  1. never 一般是 TypeScript 主动推断出来的
TypeScript
// 指定b的类型为string
let b:string
// 给b设置一个值
b = "hello"
if(typeof b === "string"){
  b.toUpperCase()
}else{
  console.log(b);
  // TypeScript会推断出此处的b是never,因为没有任何一值符合此处的逻辑
}
// & 同时满足
let c: string & number
// 此处c的类型是never
  1. never 也可以用于限制函数的返回值
TypeScript
function demo():never{
  throw new Error("程序异常退出")
}
2.4.5 void

viod 的含义是:undefined ,严格模式下不能将 null 赋值给 void 类型

TypeScript
let a: void = undefined;

// 严格模式下,该行会有警告:不能将 null 分配给类型 void
// let b:void = null;

void 常用与限制函数返回值

typescript
// 无警告
function demo1(): void {}

// 无警告
function demo2(): void {
  return;
}

// 无警告
function demo3(): void {
  return undefined;
}

let demo4 = (): void => {
  // return 666; // 有警告:不能将类型“number”分配给类型“void”
};
2.4.6 object
  1. object 的含义:任何【非原始值类型】,包括:对象、函数、数组等,范围较广。一般不用于类型限制
typescript
let a:object //a的值可以是任何【⾮原始值类型】,包括:对象、函数、数组等
// 以下代码,是将【⾮原始类型】赋给a,所以均⽆警告
a = {}
a = {name:'张三'}
a = [1,3,5,7,9]
a = function(){}
// 以下代码,是将【原始类型】赋给a,有警告
a = null // 警告:不能将类型“null”分配给类型“object”
a = undefined // 警告:不能将类型“undefined”分配给类型“object”
a = 1 // 警告:不能将类型“number”分配给类型“object”
a = true // 警告:不能将类型“boolean”分配给类型“object”
a = '你好' // 警告:不能将类型“string”分配给类型“object”
  1. Object 的含义: Object 的实例对象,限制的范围太大了,几乎不用。
typescript
let a:Object //a的值必须是Object的实例对象,
// 以下代码,均⽆警告,因为给a赋的值,都是Object的实例对象
a = {}
a = {name:'张三'}
a = [1,3,5,7,9]
a = function(){}
a = 1 // 1不是Object的实例对象,但其包装对象是Object的实例
a = true // truue不是Object的实例对象,但其包装对象是Object的实例
a = '你好' // “你好”不是Object的实例对象,但其包装对象是Object的实例
// 以下代码均有警告
a = null // 警告:不能将类型“null”分配给类型“Object”
a = undefined // 警告:不能将类型“undefined”分配给类型“Object”
  1. 在实际开发中,限制一般对象,通常使用以下形式

语法:"let 对象名: { 属性1: 类型; 属性2?: 类型; ...[propName: string]: any}"

  • ? 表示该属性是可选的

  • 【[key: string]: any】 表示任意字符串的属性名和任意的类型

typescript
// 限制person对象的具体内容,使用【,】分割,【?】代表可选属性
let person: { name: string, age?: number };

// 限制car对象的具体内容,使用【;】分割,必须有price和color属性,其它属性不去限制,有没有都行 【[propName: string]: any 】表示任意属性
let car: { price: number; color: string; [propName: string]: any };

// 限制student对象的具体内容,使用【回车】分割
let student: {
  id: string
  grade: number
};

// 以下代码均无警告
person = {name: "Tom"}
person = {name: "Tom", age: 18}
car = {price:100, color:'pink'}
student = {id: '001', grade: 3}
  1. 限制函数的参数,返回值,使用以下形式

    语法:let 函数名: (形参1: 类型,形参2: 类型,...)=> 返回值类型

typescript
let demo: (a: string, b: number) => string;
// 显示函数的参数类型和返回值类型
demo = (x, y) => {
  return x + y;
};
// 下面函数有警告
demo = (x, y ,z) => {
  return x + y;
}
  1. 限制数组,使用以下形式

    语法:类型[] 或 Array<类型>

typescript
let arr1: number[]; // 限制arr1只能存储数字
let arr2: Array<number>; // 等价于上一行代码
let arr3: string[]; // 限制aar3只能存储字符串
let arr4: Array<string>; // 等价于上一行代码

arr1 = [1, 2, 3];
arr3 = ["a", "b", "c"];

tips: 在开发中数组中一般存储相同类型的数据

2.4.7 tuple

tuple 就是一个固定长度的数组

语法:[类型, 类型, 类型, ....]

typescript
let t: [string, number];

t = ["hello", 123];

// 警告,不能将类型“[string, number, boolean]”分配给类型“[string, number]”
// t = ["hello", 123, true];
// 长度和类型必须一致
2.4.8 enum

enum(枚举):将所有可能的情况列举出来

TypeScript
// 定义一个枚举
enum Color {
  Red,
  Green,
  Blue,
}

// 定义一个枚举,并指定其初始数值
enum Color2 {
  Red = 1,
  Green,
  Blue,
}

console.log(Color);
/* 
{ '0': 'Red', '1': 'Green', '2': 'Blue', Red: 0, Green: 1, Blue: 2 }
*/
console.log(Color2);
/* 
{ '1': 'Red', '2': 'Green', '3': 'Blue', Red: 1, Green: 2, Blue: 3 }
*/

// 定义一个phone变量,并设置对其进行限制
let phone: { name: string; price: number; color: Color };

phone = { name: "华为", price: 2000, color: Color.Red };
phone = { name: "iphone", price: 6000, color: Color.Green };

if (phone.color === Color.Red) {
  console.log("手机是红色的");
}

2.4 | &

| 表示或:满足一种类型即可

typescript
let m: string | number; // m的类型为 string或muber
m = "123";
m = 12;

&表示与:表示同时满足的类型

typescript
let j: string & number; // j的类型同时为string和number
j = "123"; // 报错 j的类型为never

let k: {name:string} & {age:number}; // k同时拥有name和age属性
k = {
    name:"张三",
    age:19
}

2.5 自定义类型

为类型起一个别名,更加灵活的限制类型

TypeScript
// 性别枚举
enum Gender {
  Mael,
  Female,
}

// 自定义一个年级(高一、高二、高三)
type Grade = 1 | 2 | 3;

// 自定义一个学生类型
type Student = {
  name: string;
  age: number;
  gender: Gender;
  grade: Grade;
};

// 定义两个学生变量:s1、s2
let s1: Student;
let s2: Student;

s1 = { name: "张三", age: 18, gender: Gender.Mael, grade: 1 };
s2 = { name: "李四", age: 20, gender: Gender.Female, grade: 3 };

2.6 类型断言

在有些情况下,unknown 或 any 类型的变量的实际类型我们是明确的,但TS编译器却不清楚,此时可以通过类型断言来告诉编译器变量的类型

写法一:

变量名 as 类型

TypeScript
let a:unknown = "猜猜我的长度";  // a的类型为 unknown
let leng:number = (a as string).length; // 通过类型断言设置a的类型为 string

写法二:

<类型>变量名

typescript
let a:unknown = "猜猜我的长度";
let leng:number = (<string>a).length

三、面向对象

在ES6类的基础上进行了扩展增加了抽象类、接口、泛型...

3.1 常规类

TypeScript
class Person {
  // 定义属性
  name: string;
  age: number;
  // 使用构造函数,初始化属性(在对象创建时调用)
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    console.log(this); // this指向实例对象
  }
  // 实例方法
  sayHello() {
    console.log("hello");
  }
}

const p1 = new Person("张三", 18);
const p2 = new Person("李四", 19);
console.log(p1);
console.log(p2);

与ES6类的定义多了一个属性定义和指定参数类型的步骤

this:表示当前对象

3.2 继承(extends)

子类会拥有父类所有的属性和方法(私有属性和方法除外)

TypeScript
// 定义父类 Animal
class Animal {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  sayHello() {
    console.log(`我是${this.name}今年${this.age}岁`);
  }
}

// 定义子类 Dog 继承 Animal
class Dog extends Animal {
  color: string; // 添加子类独有的属性
  constructor(naem: string, age: number, color: string) {
    super(naem, age); // 调用父类的构造函数
    this.color = color;
  }

  // 重写父类中的方法
  sayHello() {
    console.log(`我是${this.name}今年${this.age}岁,我的颜色是${this.color}`);
  }
  // 子类独有的方法
  run() {
    console.log("小狗跑得快!!");
  }
}

const dog = new Dog("旺财", 2, "黑色");
console.log(dog);
dog.sayHello();
dog.run();

使用 extends 关键字继承父类的属性和方法

super 表示当前类的父类

在constructor 中使用 spuer() 调用父类中的构造方法

3.3 抽象类

不能去实例化,只能被别人继承,抽象类里可以有抽象方法(抽象方法:abstract开头,没有方法体)

TypeScript
// 定义抽象类
abstract class Animal {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
    
  // 抽象方法:abstract开头,没有方法体
  // 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
  abstract sayHello(): void; // :void返回值类型
  普通方法...
}

// 定义子类 Dog 继承 Animal
class Dog extends Animal {
  // 添加子类独有的属性
  color: string;
  constructor(naem: string, age: number, color: string) {
    super(naem, age); // 调用父类的构造函数
    this.color = color;
  }
  // 重写父类中的方法
  sayHello() {
    console.log(`我是${this.name}今年${this.age}岁,我的颜色是${this.color}`);
  }
}

const dog = new Dog("旺财", 2, "黑色");
console.log(dog);
dog.sayHello();

// const animal = new Animal("蛇", 3); // 警告:无法创建抽象类的实例

抽象类:在 class 前加 abstract ,不能被实例化

抽象方法:在方法名前加 abstract,没有方法体

注意:抽象方法只能被定义在抽象类中,且子类必须对抽象方法进行重写

3.4 接口

  1. 接口是用来限制类中包含哪些属性和方法
typescript
// 使用接口来限制类的结构,实现了该接口的类必须要满足接口定义的属性和方法
interface myInter {
  name: string;
  asyHello(): void;
}

// 实现接口就是使类满足接口的要求
class Myclass implements myInter {
  // 必须要有name属性
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  // 必须要有asyHello方法
  asyHello() {
    console.log("hello");
  }
  .....
}

使用 implements 去实现接口

实现了该接口的类必须要满足接口定义的属性和方法

  1. 接口是可以重复声明的

接口重复声明内的属性和方法会进行追加

TypeScript
interface myInterface {
  // 应该具有的属性
  name: string;
  age: number;
  // 应该具有的方法
  getAge(): number;
}

interface myInterface {
  // 新增的属性
  sex: string;
}

const obj: myInterface = {
  name: "李四",
  age: 20,
  sex: "男",
  getAge() {
    return this.age;
  },
};
  1. 接口 与 自定义类型 的区别

接口可以:

  1. 当自定义类型去使用
  2. 可以限制类的结构

自定义类型:

  1. 仅仅就是自定义类型
TypeScript
//  描述一个对象的类型:自定义类型
type myType = {
  // 应该具有的属性
  name: string;
  age: number;
  // 应该具有的方法
  getAge(): number;
};

const obj1: myType = {
  name: "张三",
  age: 18,
  getAge() {
    return this.age;
  },
};

// 接口就是用来定义一个类的结构,类中包含哪些属性和方法
interface myInterface {
  // 应该具有的属性
  name: string;
  age: number;
  // 应该具有的方法
  getAge(): number;
}

// 接口做为自定义类型使用
const obj2: myInterface = {
  name: "李四",
  age: 20,
  getAge() {
    return this.age;
  },
};
  1. 接口 与 抽象类 的区别

接口:(父类对子类的一种限制)

  1. 只能有抽象类,只定义结构,而不考虑实际值,所有的属性都不能有实际的值
  2. 使用 implements 关键字去实现接口
  3. 子类不会继承到父类任何东西

抽象类:(父类提供给子类继承使用)

  1. 可以有普通方法,也可以有抽象方法
  2. 使用 extends 关键字去继承抽象类
  3. 子类可以继承到父类属性和方法

抽象类举例:

typescript
// 抽象类中可以有抽象方法也可以有普通方法
abstract class Person {
  // 属性
  name: string;
  age: number;
  // 构造器
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  // 抽象方法
  abstract speak(): void;
  // 普通方法
  eat() {
    console.log("吃饭");
  }
}
// Teacher类继承抽象类Person
class Teacher extends Person {
  constructor(name: string, age: number) {
    super(name, age); // 调用抽象类的构造器
  }
  // 重写抽象方法
  speak(): void {
    console.log("老师说话");
  }
}

接口举例:

typescript
interface Person {
  // 属性,不写具体指
  name: string;
  age: number;
  // 方法,不写具体实现
  speak(): void;
}

// 创建Teacher类实现Person接口
class Teacher implements Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  speak() {
    console.log("我是一个老师");
  }
}

3.5 属性修饰符

修饰符描述作用
readyonly只读属性属性无法修改
public(默认值)公开的可以在类、子类和对象中访问(修改)
protected受保护的可以在类,子类中访问(修改)
private私有的只能在类中访问(修改)
typescript
class A {
  readonly name: string;
  public age: number;
  protected sex: string;
  private tel: string;
  constructor(name: string, age: number, sex: string, tel: string) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.tel = tel;
  }
  test() {
    console.log(this.name);
    console.log(this.age);
    console.log(this.sex);
    console.log(this.tel);
  }
}

class B extends A {
  constructor(name: string, age: number, sex: string, tel: string) {
    super(name, age, sex, tel);
  }
  test() {
    console.log(this.name);
    console.log(this.age);
    console.log(this.sex);
    // protected修饰的属性不能在子类中访问
    // console.log(this.tel); // 警告 tel私有只能在A中访问
  }
}

const b = new B("李四", 19, "男", "123456789");
console.log(b.name);
console.log(b.age);
// protected和private修饰的属性不能在对象中访问
// console.log(b.sex); // 警告 sex受保护只能在A和A的子类中访问
// console.log(b.tel);// 警告 tel私有只能在A中访问

3.6 属性的封装

  1. 自定义getter、setter方法
typescript
class Person {
    // TS 可以在属性前添加属性的修饰符
    private name: string;
    private age: number;

    constructor(name: string, age: number) {
      this.name = name;
      this.age = age;
    }
    // 通过公共的方法读取(修改)属性值
    getName() {
      return this.name;
    }
    setName(name: string) {
      this.name = name;
    }
    getAge() {
      return this.age;
    }
    setAge(age: number) {
      if (age >= 0) {
        this.age = age;
      }
    }
  }

  const per = new Person("张三", 18);
  // 若使用public,属性可以任意的修改,将会导致对象中的数据变得不安全
  // per.name = "李四"; // // 警告:属性name为私有属性 只能在类的内部访问
  // per.age = -20; // // 警告:属性name为私有属性 只能在类的内部访问
  // console.log(per.name); // 警告:属性name为私有属性 只能在类的内部访问
 // 通过公共方法去访问(修改)私有属性
  console.log(per.getName());
  per.setName("李四");
  console.log(per.getName());
  per.setAge(-18);
  console.log(per.getAge()); //18
  1. 使用 ES6/TS 中的 getter、setter方法
TypeScript
// 定义一个表示人的类
class Person {
  // TS 可以在属性前添加属性的修饰符
  private _name: string;
  private _age: number;

  constructor(name: string, age: number) {
    this._name = name;
    this._age = age;
  }

  // TS中设置getter setter方法的方式
  get name() {
    // 获取name属性时自动调用
    return this._name;
  }
  set name(value: string) {
    // 修改name属性时自动调用
    this._name = value;
  }
  get age() {
    return this._age;
  }
  set age(value: number) {
    if (value >= 0) {
      this._age = value;
    }
  }
}

const per = new Person("张三", 18);
// console.log(per._name); // 警告:name为私有属性 只能在类的内部访问
console.log(per.name, per.age); // 张三 18
per.name = "李四";
per.age = -20;
console.log(per.name, per.age); // 李四 18

3.7 类定义的简写形式

普通写法:

先声明属性,在使用构造函数进行初始化

TypeScript
class C {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
class D extends C {
  sex: string;
  constructor(name: string, age: number, sex: string) {
    super(name, age);
    this.sex = sex;
  }
}

简化写法:

直接将属性定义在构造函数中,不用再写属性声明和构造函数中的属性初始化

TypeScript
class F {
  //构造函数参数必须写上属性修饰符和类型,否则报错
  constructor(public name: string, public age: number) {}
}
class E extends F {
  constructor(name: string, age: number, public sex: string) {
    super(name, age);
  }
}

const e = new E("Tom", 18, "male");
console.log(e);// E { name: 'Tom', age: 18, sex: 'male' }

特别注意:构造函数参数必须写上属性修饰符和类型,否则报错

3.8 泛型

定义⼀个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时就需要泛型了

<T> 就是泛型,(T可以为其他字母),设置泛型后即可在函数中使用 T 来表示该类型:

TypeScript
// <T> 就是一个泛型
function fn<T>(a: T): T {
  return a;
}

// 可以直接去调用具有泛型的函数
fn(10); // TS会自动推断T的类型为number
fn<string>("hello"); // 手动指定T的类型为string

在函数调用时指定其类型

泛型可以写多个

TypeScript
function fn2<T, K>(a: T, b: K): T {
  console.log(b);
  return a;
}
// 为多个泛型指定其类型
let result3 = fn2<string, number>("1", 2);

对泛型的范围进行约束

TypeScript
interface Inter {
  lengh: number;
}
// T extends Inter 表示泛型T必须是Inter的子类
function fn3<T extends Inter>(a: T): number {
  return a.lengh;
}

fn3({ lengh: 10 });

在类中使用泛型

TypeScript
// 在类中使用泛型
class MyClass<T> {
  name: T; // 不确定name的类型,使用变量表示类型
  constructor(name: T) {
    this.name = name;
  }
}
const mc = new MyClass<string>("张三"); // 在实例化的时候指定泛型T的类型